nmatrix 0.0.8 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -8
  3. data/.rspec +1 -1
  4. data/.travis.yml +12 -0
  5. data/CONTRIBUTING.md +27 -12
  6. data/Gemfile +1 -0
  7. data/History.txt +38 -0
  8. data/Manifest.txt +15 -15
  9. data/README.rdoc +7 -6
  10. data/Rakefile +40 -5
  11. data/ext/nmatrix/data/data.cpp +2 -37
  12. data/ext/nmatrix/data/data.h +19 -121
  13. data/ext/nmatrix/data/meta.h +70 -0
  14. data/ext/nmatrix/extconf.rb +40 -12
  15. data/ext/nmatrix/math/math.h +13 -103
  16. data/ext/nmatrix/nmatrix.cpp +10 -2018
  17. data/ext/nmatrix/nmatrix.h +16 -13
  18. data/ext/nmatrix/ruby_constants.cpp +12 -1
  19. data/ext/nmatrix/ruby_constants.h +7 -1
  20. data/ext/nmatrix/ruby_nmatrix.c +2169 -0
  21. data/ext/nmatrix/storage/dense.cpp +123 -14
  22. data/ext/nmatrix/storage/dense.h +10 -4
  23. data/ext/nmatrix/storage/list.cpp +265 -48
  24. data/ext/nmatrix/storage/list.h +6 -9
  25. data/ext/nmatrix/storage/storage.cpp +44 -54
  26. data/ext/nmatrix/storage/storage.h +2 -2
  27. data/ext/nmatrix/storage/yale/class.h +1070 -0
  28. data/ext/nmatrix/storage/yale/iterators/base.h +142 -0
  29. data/ext/nmatrix/storage/yale/iterators/iterator.h +130 -0
  30. data/ext/nmatrix/storage/yale/iterators/row.h +449 -0
  31. data/ext/nmatrix/storage/yale/iterators/row_stored.h +139 -0
  32. data/ext/nmatrix/storage/yale/iterators/row_stored_nd.h +167 -0
  33. data/ext/nmatrix/storage/yale/iterators/stored_diagonal.h +123 -0
  34. data/ext/nmatrix/storage/yale/math/transpose.h +110 -0
  35. data/ext/nmatrix/storage/yale/yale.cpp +1785 -0
  36. data/ext/nmatrix/storage/{yale.h → yale/yale.h} +23 -55
  37. data/ext/nmatrix/types.h +2 -0
  38. data/ext/nmatrix/util/io.cpp +27 -45
  39. data/ext/nmatrix/util/io.h +0 -2
  40. data/ext/nmatrix/util/sl_list.cpp +169 -28
  41. data/ext/nmatrix/util/sl_list.h +9 -3
  42. data/lib/nmatrix/blas.rb +20 -20
  43. data/lib/nmatrix/enumerate.rb +1 -1
  44. data/lib/nmatrix/io/mat5_reader.rb +8 -14
  45. data/lib/nmatrix/lapack.rb +3 -3
  46. data/lib/nmatrix/math.rb +3 -3
  47. data/lib/nmatrix/nmatrix.rb +19 -5
  48. data/lib/nmatrix/nvector.rb +2 -0
  49. data/lib/nmatrix/shortcuts.rb +90 -125
  50. data/lib/nmatrix/version.rb +1 -1
  51. data/nmatrix.gemspec +7 -8
  52. data/spec/{nmatrix_spec.rb → 00_nmatrix_spec.rb} +45 -208
  53. data/spec/01_enum_spec.rb +184 -0
  54. data/spec/{slice_spec.rb → 02_slice_spec.rb} +55 -39
  55. data/spec/blas_spec.rb +22 -54
  56. data/spec/elementwise_spec.rb +9 -8
  57. data/spec/io_spec.rb +6 -4
  58. data/spec/lapack_spec.rb +26 -26
  59. data/spec/math_spec.rb +9 -5
  60. data/spec/nmatrix_yale_spec.rb +29 -61
  61. data/spec/shortcuts_spec.rb +34 -22
  62. data/spec/slice_set_spec.rb +157 -0
  63. data/spec/spec_helper.rb +42 -2
  64. data/spec/stat_spec.rb +192 -0
  65. metadata +52 -55
  66. data/ext/nmatrix/storage/yale.cpp +0 -2284
  67. data/spec/nmatrix_list_spec.rb +0 -113
  68. data/spec/nvector_spec.rb +0 -112
@@ -198,7 +198,6 @@
198
198
  */
199
199
 
200
200
  #define NM_NUM_DTYPES 13 // data/data.h
201
- #define NM_NUM_ITYPES 4 // data/data.h
202
201
  #define NM_NUM_STYPES 3 // storage/storage.h
203
202
 
204
203
  //#ifdef __cplusplus
@@ -225,12 +224,6 @@ NM_DEF_ENUM(dtype_t, BYTE = 0, // unsigned char
225
224
  RATIONAL128 = 11, // Rational128 class
226
225
  RUBYOBJ = 12); // Ruby VALUE type
227
226
 
228
- /* Index Type for Yale Matrices */
229
- NM_DEF_ENUM(itype_t, UINT8 = 0,
230
- UINT16 = 1,
231
- UINT32 = 2,
232
- UINT64 = 3);
233
-
234
227
  NM_DEF_ENUM(symm_t, NONSYMM = 0,
235
228
  SYMM = 1,
236
229
  SKEW = 2,
@@ -253,11 +246,10 @@ NM_DEF_STORAGE_STRUCT_POST(DENSE_STORAGE); // };
253
246
 
254
247
  /* Yale Storage */
255
248
  NM_DEF_STORAGE_CHILD_STRUCT_PRE(YALE_STORAGE);
256
- void* a; // should go first
257
- size_t ndnz; // Strictly non-diagonal non-zero count!
249
+ void* a; // should go first
250
+ size_t ndnz; // Strictly non-diagonal non-zero count!
258
251
  size_t capacity;
259
- NM_DECL_ENUM(itype_t, itype);
260
- void* ija;
252
+ size_t* ija;
261
253
  NM_DEF_STORAGE_STRUCT_POST(YALE_STORAGE);
262
254
 
263
255
  // FIXME: NODE and LIST should be put in some kind of namespace or something, at least in C++.
@@ -306,14 +298,14 @@ NM_DEF_STRUCT_POST(NMATRIX); // };
306
298
  #define NM_SRC(val) (NM_STORAGE(val)->src)
307
299
  #define NM_DIM(val) (NM_STORAGE(val)->dim)
308
300
  #define NM_DTYPE(val) (NM_STORAGE(val)->dtype)
309
- #define NM_ITYPE(val) (NM_STORAGE_YALE(val)->itype)
310
301
  #define NM_STYPE(val) (NM_STRUCT(val)->stype)
311
302
  #define NM_SHAPE(val,i) (NM_STORAGE(val)->shape[(i)])
312
303
  #define NM_SHAPE0(val) (NM_STORAGE(val)->shape[0])
313
304
  #define NM_SHAPE1(val) (NM_STORAGE(val)->shape[1])
314
305
  #define NM_DEFAULT_VAL(val) (NM_STORAGE_LIST(val)->default_val)
315
306
 
316
- #define NM_DENSE_COUNT(val) (storage_count_max_elements(NM_STORAGE_DENSE(val)))
307
+ #define NM_DENSE_COUNT(val) (nm_storage_count_max_elements(NM_STORAGE_DENSE(val)))
308
+ #define NM_DENSE_ELEMENTS(val) (NM_STORAGE_DENSE(val)->elements)
317
309
  #define NM_SIZEOF_DTYPE(val) (DTYPE_SIZES[NM_DTYPE(val)])
318
310
  #define NM_REF(val,slice) (RefFuncs[NM_STYPE(val)]( NM_STORAGE(val), slice, NM_SIZEOF_DTYPE(val) ))
319
311
 
@@ -333,6 +325,10 @@ NM_DEF_STRUCT_POST(NMATRIX); // };
333
325
  #define NM_IsNVector(obj) \
334
326
  (rb_obj_is_kind_of(obj, cNVector) == Qtrue)
335
327
 
328
+ #define RB_P(OBJ) \
329
+ rb_funcall(rb_stderr, rb_intern("print"), 1, rb_funcall(OBJ, rb_intern("object_id"), 0)); \
330
+ rb_funcall(rb_stderr, rb_intern("puts"), 1, rb_funcall(OBJ, rb_intern("inspect"), 0));
331
+
336
332
 
337
333
  #ifdef __cplusplus
338
334
  typedef VALUE (*METHOD)(...);
@@ -345,6 +341,7 @@ typedef VALUE (*METHOD)(...);
345
341
  */
346
342
 
347
343
  #ifdef __cplusplus
344
+
348
345
  extern "C" {
349
346
  #endif
350
347
 
@@ -359,8 +356,14 @@ extern "C" {
359
356
 
360
357
  // Non-API functions needed by other cpp files.
361
358
  NMATRIX* nm_create(nm::stype_t stype, STORAGE* storage);
359
+ NMATRIX* nm_cast_with_ctype_args(NMATRIX* self, nm::stype_t new_stype, nm::dtype_t new_dtype, void* init_ptr);
360
+ VALUE nm_cast(VALUE self, VALUE new_stype_symbol, VALUE new_dtype_symbol, VALUE init);
361
+ void nm_mark(NMATRIX* mat);
362
362
  void nm_delete(NMATRIX* mat);
363
363
  void nm_delete_ref(NMATRIX* mat);
364
+ void nm_mark(NMATRIX* mat);
365
+ void nm_register_values(VALUE* vals, size_t n);
366
+ void nm_unregister_values(VALUE* vals, size_t n);
364
367
 
365
368
  #ifdef __cplusplus
366
369
  }
@@ -35,7 +35,13 @@
35
35
  * Global Variables
36
36
  */
37
37
 
38
- ID nm_rb_real,
38
+ ID nm_rb_dtype,
39
+ nm_rb_stype,
40
+
41
+ nm_rb_capacity,
42
+ nm_rb_default,
43
+
44
+ nm_rb_real,
39
45
  nm_rb_imag,
40
46
 
41
47
  nm_rb_numer,
@@ -93,6 +99,11 @@ VALUE cNMatrix,
93
99
  */
94
100
 
95
101
  void nm_init_ruby_constants(void) {
102
+ nm_rb_dtype = rb_intern("dtype");
103
+ nm_rb_stype = rb_intern("stype");
104
+
105
+ nm_rb_capacity = rb_intern("capacity");
106
+ nm_rb_default = rb_intern("default");
96
107
 
97
108
  nm_rb_real = rb_intern("real");
98
109
  nm_rb_imag = rb_intern("imag");
@@ -38,7 +38,13 @@
38
38
  * Data
39
39
  */
40
40
 
41
- extern ID nm_rb_real,
41
+ extern ID nm_rb_dtype,
42
+ nm_rb_stype,
43
+
44
+ nm_rb_capacity,
45
+ nm_rb_default,
46
+
47
+ nm_rb_real,
42
48
  nm_rb_imag,
43
49
 
44
50
  nm_rb_numer,
@@ -0,0 +1,2169 @@
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 - 2013, Ruby Science Foundation
13
+ // NMatrix is Copyright (c) 2013, 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
+
55
+ static SLICE* get_slice(size_t dim, int argc, VALUE* arg, size_t* shape);
56
+ static VALUE nm_xslice(int argc, VALUE* argv, void* (*slice_func)(const STORAGE*, SLICE*), void (*delete_func)(NMATRIX*), VALUE self);
57
+ static VALUE nm_mset(int argc, VALUE* argv, VALUE self);
58
+ static VALUE nm_mget(int argc, VALUE* argv, VALUE self);
59
+ static VALUE nm_mref(int argc, VALUE* argv, VALUE self);
60
+ static VALUE nm_is_ref(VALUE self);
61
+
62
+ static VALUE is_symmetric(VALUE self, bool hermitian);
63
+
64
+ static VALUE nm_guess_dtype(VALUE self, VALUE v);
65
+ static VALUE nm_min_dtype(VALUE self, VALUE v);
66
+
67
+ /*
68
+ * Macro defines an element-wise accessor function for some operation.
69
+ *
70
+ * This is only responsible for the Ruby accessor! You still have to write the actual functions, obviously.
71
+ */
72
+ #define DEF_ELEMENTWISE_RUBY_ACCESSOR(oper, name) \
73
+ static VALUE nm_ew_##name(VALUE left_val, VALUE right_val) { \
74
+ return elementwise_op(nm::EW_##oper, left_val, right_val); \
75
+ }
76
+
77
+ /*
78
+ * Macro declares a corresponding accessor function prototype for some element-wise operation.
79
+ */
80
+ #define DECL_ELEMENTWISE_RUBY_ACCESSOR(name) static VALUE nm_ew_##name(VALUE left_val, VALUE right_val);
81
+
82
+ DECL_ELEMENTWISE_RUBY_ACCESSOR(add)
83
+ DECL_ELEMENTWISE_RUBY_ACCESSOR(subtract)
84
+ DECL_ELEMENTWISE_RUBY_ACCESSOR(multiply)
85
+ DECL_ELEMENTWISE_RUBY_ACCESSOR(divide)
86
+ DECL_ELEMENTWISE_RUBY_ACCESSOR(power)
87
+ DECL_ELEMENTWISE_RUBY_ACCESSOR(mod)
88
+ DECL_ELEMENTWISE_RUBY_ACCESSOR(eqeq)
89
+ DECL_ELEMENTWISE_RUBY_ACCESSOR(neq)
90
+ DECL_ELEMENTWISE_RUBY_ACCESSOR(lt)
91
+ DECL_ELEMENTWISE_RUBY_ACCESSOR(gt)
92
+ DECL_ELEMENTWISE_RUBY_ACCESSOR(leq)
93
+ DECL_ELEMENTWISE_RUBY_ACCESSOR(geq)
94
+
95
+ static VALUE elementwise_op(nm::ewop_t op, VALUE left_val, VALUE right_val);
96
+
97
+ static VALUE nm_symmetric(VALUE self);
98
+ static VALUE nm_hermitian(VALUE self);
99
+
100
+ static VALUE nm_eqeq(VALUE left, VALUE right);
101
+
102
+ static VALUE matrix_multiply_scalar(NMATRIX* left, VALUE scalar);
103
+ static VALUE matrix_multiply(NMATRIX* left, NMATRIX* right);
104
+ static VALUE nm_multiply(VALUE left_v, VALUE right_v);
105
+ static VALUE nm_det_exact(VALUE self);
106
+ static VALUE nm_complex_conjugate_bang(VALUE self);
107
+
108
+ static nm::dtype_t interpret_dtype(int argc, VALUE* argv, nm::stype_t stype);
109
+ static void* interpret_initial_value(VALUE arg, nm::dtype_t dtype);
110
+ static size_t* interpret_shape(VALUE arg, size_t* dim);
111
+ static nm::stype_t interpret_stype(VALUE arg);
112
+
113
+ /* Singleton methods */
114
+ static VALUE nm_upcast(VALUE self, VALUE t1, VALUE t2);
115
+
116
+
117
+ #ifdef BENCHMARK
118
+ static double get_time(void);
119
+ #endif
120
+
121
+ ///////////////////
122
+ // Ruby Bindings //
123
+ ///////////////////
124
+
125
+ void Init_nmatrix() {
126
+
127
+
128
+ ///////////////////////
129
+ // Class Definitions //
130
+ ///////////////////////
131
+
132
+ cNMatrix = rb_define_class("NMatrix", rb_cObject);
133
+ //cNVector = rb_define_class("NVector", cNMatrix);
134
+
135
+ // Special exceptions
136
+
137
+ /*
138
+ * Exception raised when there's a problem with data.
139
+ */
140
+ nm_eDataTypeError = rb_define_class("DataTypeError", rb_eStandardError);
141
+
142
+ /*
143
+ * Exception raised when something goes wrong with the storage of a matrix.
144
+ */
145
+ nm_eStorageTypeError = rb_define_class("StorageTypeError", rb_eStandardError);
146
+
147
+ ///////////////////
148
+ // Class Methods //
149
+ ///////////////////
150
+
151
+ rb_define_alloc_func(cNMatrix, nm_alloc);
152
+
153
+ ///////////////////////
154
+ // Singleton Methods //
155
+ ///////////////////////
156
+
157
+ rb_define_singleton_method(cNMatrix, "upcast", (METHOD)nm_upcast, 2); /* in ext/nmatrix/nmatrix.cpp */
158
+ rb_define_singleton_method(cNMatrix, "guess_dtype", (METHOD)nm_guess_dtype, 1);
159
+ rb_define_singleton_method(cNMatrix, "min_dtype", (METHOD)nm_min_dtype, 1);
160
+
161
+ //////////////////////
162
+ // Instance Methods //
163
+ //////////////////////
164
+
165
+ rb_define_method(cNMatrix, "initialize", (METHOD)nm_init, -1);
166
+ rb_define_method(cNMatrix, "initialize_copy", (METHOD)nm_init_copy, 1);
167
+ rb_define_singleton_method(cNMatrix, "read", (METHOD)nm_read, -1);
168
+
169
+ rb_define_method(cNMatrix, "write", (METHOD)nm_write, -1);
170
+
171
+ // Technically, the following function is a copy constructor.
172
+ rb_define_method(cNMatrix, "transpose", (METHOD)nm_init_transposed, 0);
173
+
174
+ rb_define_method(cNMatrix, "dtype", (METHOD)nm_dtype, 0);
175
+ rb_define_method(cNMatrix, "stype", (METHOD)nm_stype, 0);
176
+ rb_define_method(cNMatrix, "cast_full", (METHOD)nm_cast, 3);
177
+ rb_define_method(cNMatrix, "default_value", (METHOD)nm_default_value, 0);
178
+ rb_define_protected_method(cNMatrix, "__list_default_value__", (METHOD)nm_list_default_value, 0);
179
+ rb_define_protected_method(cNMatrix, "__yale_default_value__", (METHOD)nm_yale_default_value, 0);
180
+
181
+ rb_define_method(cNMatrix, "[]", (METHOD)nm_mref, -1);
182
+ rb_define_method(cNMatrix, "slice", (METHOD)nm_mget, -1);
183
+ rb_define_method(cNMatrix, "[]=", (METHOD)nm_mset, -1);
184
+ rb_define_method(cNMatrix, "is_ref?", (METHOD)nm_is_ref, 0);
185
+ rb_define_method(cNMatrix, "dimensions", (METHOD)nm_dim, 0);
186
+ rb_define_method(cNMatrix, "effective_dimensions", (METHOD)nm_effective_dim, 0);
187
+
188
+ rb_define_protected_method(cNMatrix, "__list_to_hash__", (METHOD)nm_to_hash, 0); // handles list and dense, which are n-dimensional
189
+
190
+ rb_define_method(cNMatrix, "shape", (METHOD)nm_shape, 0);
191
+ rb_define_method(cNMatrix, "supershape", (METHOD)nm_supershape, 0);
192
+ rb_define_method(cNMatrix, "offset", (METHOD)nm_offset, 0);
193
+ rb_define_method(cNMatrix, "det_exact", (METHOD)nm_det_exact, 0);
194
+ rb_define_method(cNMatrix, "complex_conjugate!", (METHOD)nm_complex_conjugate_bang, 0);
195
+
196
+ rb_define_protected_method(cNMatrix, "__dense_each__", (METHOD)nm_dense_each, 0);
197
+ rb_define_protected_method(cNMatrix, "__dense_map__", (METHOD)nm_dense_map, 0);
198
+ rb_define_protected_method(cNMatrix, "__dense_map_pair__", (METHOD)nm_dense_map_pair, 1);
199
+ rb_define_method(cNMatrix, "each_with_indices", (METHOD)nm_each_with_indices, 0);
200
+ rb_define_method(cNMatrix, "each_stored_with_indices", (METHOD)nm_each_stored_with_indices, 0);
201
+ rb_define_method(cNMatrix, "each_ordered_stored_with_indices", (METHOD)nm_each_ordered_stored_with_indices, 0);
202
+ rb_define_protected_method(cNMatrix, "__list_map_merged_stored__", (METHOD)nm_list_map_merged_stored, 2);
203
+ rb_define_protected_method(cNMatrix, "__yale_map_merged_stored__", (METHOD)nm_yale_map_merged_stored, 2);
204
+ rb_define_protected_method(cNMatrix, "__yale_map_stored__", (METHOD)nm_yale_map_stored, 0);
205
+ rb_define_protected_method(cNMatrix, "__yale_stored_diagonal_each_with_indices__", (METHOD)nm_yale_stored_diagonal_each_with_indices, 0);
206
+ rb_define_protected_method(cNMatrix, "__yale_stored_nondiagonal_each_with_indices__", (METHOD)nm_yale_stored_nondiagonal_each_with_indices, 0);
207
+
208
+ rb_define_method(cNMatrix, "==", (METHOD)nm_eqeq, 1);
209
+
210
+ rb_define_method(cNMatrix, "+", (METHOD)nm_ew_add, 1);
211
+ rb_define_method(cNMatrix, "-", (METHOD)nm_ew_subtract, 1);
212
+ rb_define_method(cNMatrix, "*", (METHOD)nm_ew_multiply, 1);
213
+ rb_define_method(cNMatrix, "/", (METHOD)nm_ew_divide, 1);
214
+ rb_define_method(cNMatrix, "**", (METHOD)nm_ew_power, 1);
215
+ rb_define_method(cNMatrix, "%", (METHOD)nm_ew_mod, 1);
216
+
217
+ rb_define_method(cNMatrix, "=~", (METHOD)nm_ew_eqeq, 1);
218
+ rb_define_method(cNMatrix, "!~", (METHOD)nm_ew_neq, 1);
219
+ rb_define_method(cNMatrix, "<=", (METHOD)nm_ew_leq, 1);
220
+ rb_define_method(cNMatrix, ">=", (METHOD)nm_ew_geq, 1);
221
+ rb_define_method(cNMatrix, "<", (METHOD)nm_ew_lt, 1);
222
+ rb_define_method(cNMatrix, ">", (METHOD)nm_ew_gt, 1);
223
+
224
+ /////////////////////////////
225
+ // Helper Instance Methods //
226
+ /////////////////////////////
227
+ rb_define_protected_method(cNMatrix, "__yale_vector_set__", (METHOD)nm_vector_set, -1);
228
+
229
+ /////////////////////////
230
+ // Matrix Math Methods //
231
+ /////////////////////////
232
+ rb_define_method(cNMatrix, "dot", (METHOD)nm_multiply, 1);
233
+
234
+ rb_define_method(cNMatrix, "symmetric?", (METHOD)nm_symmetric, 0);
235
+ rb_define_method(cNMatrix, "hermitian?", (METHOD)nm_hermitian, 0);
236
+
237
+ rb_define_method(cNMatrix, "capacity", (METHOD)nm_capacity, 0);
238
+
239
+ /////////////
240
+ // Aliases //
241
+ /////////////
242
+
243
+ rb_define_alias(cNMatrix, "dim", "dimensions");
244
+ rb_define_alias(cNMatrix, "effective_dim", "effective_dimensions");
245
+ rb_define_alias(cNMatrix, "equal?", "eql?");
246
+
247
+ ///////////////////////
248
+ // Symbol Generation //
249
+ ///////////////////////
250
+
251
+ nm_init_ruby_constants();
252
+
253
+ //////////////////////////
254
+ // YaleFunctions module //
255
+ //////////////////////////
256
+
257
+ nm_init_yale_functions();
258
+
259
+ /////////////////
260
+ // BLAS module //
261
+ /////////////////
262
+
263
+ nm_math_init_blas();
264
+
265
+ ///////////////
266
+ // IO module //
267
+ ///////////////
268
+ nm_init_io();
269
+
270
+ /////////////////////////////////////////////////
271
+ // Force compilation of necessary constructors //
272
+ /////////////////////////////////////////////////
273
+ nm_init_data();
274
+ }
275
+
276
+
277
+ //////////////////
278
+ // Ruby Methods //
279
+ //////////////////
280
+
281
+
282
+ /*
283
+ * Slice constructor.
284
+ */
285
+ static SLICE* alloc_slice(size_t dim) {
286
+ SLICE* slice = ALLOC(SLICE);
287
+ slice->coords = ALLOC_N(size_t, dim);
288
+ slice->lengths = ALLOC_N(size_t, dim);
289
+ return slice;
290
+ }
291
+
292
+
293
+ /*
294
+ * Slice destructor.
295
+ */
296
+ static void free_slice(SLICE* slice) {
297
+ xfree(slice->coords);
298
+ xfree(slice->lengths);
299
+ xfree(slice);
300
+ }
301
+
302
+
303
+ /*
304
+ * Allocator.
305
+ */
306
+ static VALUE nm_alloc(VALUE klass) {
307
+ NMATRIX* mat = ALLOC(NMATRIX);
308
+ mat->storage = NULL;
309
+
310
+ // DO NOT MARK This STRUCT. It has no storage allocated, and no stype, so mark will do an invalid something.
311
+ return Data_Wrap_Struct(klass, NULL, nm_delete, mat);
312
+ }
313
+
314
+ /*
315
+ * Find the capacity of an NMatrix. The capacity only differs from the size for
316
+ * Yale matrices, which occasionally allocate more space than they need. For
317
+ * list and dense, capacity gives the number of elements in the matrix.
318
+ *
319
+ * If you call this on a slice, it may behave unpredictably. Most likely it'll
320
+ * just return the original matrix's capacity.
321
+ */
322
+ static VALUE nm_capacity(VALUE self) {
323
+ VALUE cap;
324
+
325
+ switch(NM_STYPE(self)) {
326
+ case nm::YALE_STORE:
327
+ cap = UINT2NUM(reinterpret_cast<YALE_STORAGE*>(NM_STORAGE_YALE(self)->src)->capacity);
328
+ break;
329
+
330
+ case nm::DENSE_STORE:
331
+ cap = UINT2NUM(nm_storage_count_max_elements( NM_STORAGE_DENSE(self) ));
332
+ break;
333
+
334
+ case nm::LIST_STORE:
335
+ cap = UINT2NUM(nm_list_storage_count_elements( NM_STORAGE_LIST(self) ));
336
+ break;
337
+
338
+ default:
339
+ rb_raise(nm_eStorageTypeError, "unrecognized stype in nm_capacity()");
340
+ }
341
+
342
+ return cap;
343
+ }
344
+
345
+
346
+ /*
347
+ * Mark function.
348
+ */
349
+ void nm_mark(NMATRIX* mat) {
350
+ STYPE_MARK_TABLE(mark)
351
+ mark[mat->stype](mat->storage);
352
+ }
353
+
354
+
355
+ /*
356
+ * Destructor.
357
+ */
358
+ void nm_delete(NMATRIX* mat) {
359
+ static void (*ttable[nm::NUM_STYPES])(STORAGE*) = {
360
+ nm_dense_storage_delete,
361
+ nm_list_storage_delete,
362
+ nm_yale_storage_delete
363
+ };
364
+ ttable[mat->stype](mat->storage);
365
+
366
+ xfree(mat);
367
+ }
368
+
369
+ /*
370
+ * Slicing destructor.
371
+ */
372
+ void nm_delete_ref(NMATRIX* mat) {
373
+ static void (*ttable[nm::NUM_STYPES])(STORAGE*) = {
374
+ nm_dense_storage_delete_ref,
375
+ nm_list_storage_delete_ref,
376
+ nm_yale_storage_delete_ref
377
+ };
378
+ ttable[mat->stype](mat->storage);
379
+
380
+ xfree(mat);
381
+ }
382
+
383
+ /*
384
+ * Register the addresses of an array of VALUEs with the gc to avoid collection
385
+ * while using them internally.
386
+ */
387
+ void nm_register_values(VALUE* values, size_t n) {
388
+ if (values) {
389
+ for (size_t i = n; i-- > 0;) {
390
+ rb_gc_register_address(values + i);
391
+ }
392
+ }
393
+ }
394
+
395
+ /*
396
+ * Unregister the addresses of an array of VALUEs with the gc to allow normal
397
+ * garbage collection to occur again.
398
+ */
399
+ void nm_unregister_values(VALUE* values, size_t n) {
400
+ if (values) {
401
+ for (size_t i = n; i-- > 0;) {
402
+ rb_gc_unregister_address(values + i);
403
+ }
404
+ }
405
+ }
406
+
407
+ /*
408
+ * call-seq:
409
+ * dtype -> Symbol
410
+ *
411
+ * Get the data type (dtype) of a matrix, e.g., :byte, :int8, :int16, :int32,
412
+ * :int64, :float32, :float64, :complex64, :complex128, :rational32,
413
+ * :rational64, :rational128, or :object (the last is a Ruby object).
414
+ */
415
+ static VALUE nm_dtype(VALUE self) {
416
+ ID dtype = rb_intern(DTYPE_NAMES[NM_DTYPE(self)]);
417
+ return ID2SYM(dtype);
418
+ }
419
+
420
+
421
+ /*
422
+ * call-seq:
423
+ * upcast(first_dtype, second_dtype) -> Symbol
424
+ *
425
+ * Given a binary operation between types t1 and t2, what type will be returned?
426
+ *
427
+ * This is a singleton method on NMatrix, e.g., NMatrix.upcast(:int32, :int64)
428
+ */
429
+ static VALUE nm_upcast(VALUE self, VALUE t1, VALUE t2) {
430
+
431
+ nm::dtype_t d1 = nm_dtype_from_rbsymbol(t1),
432
+ d2 = nm_dtype_from_rbsymbol(t2);
433
+
434
+ return ID2SYM(rb_intern( DTYPE_NAMES[ Upcast[d1][d2] ] ));
435
+ }
436
+
437
+
438
+ /*
439
+ * call-seq:
440
+ default_value -> ...
441
+ *
442
+ * Get the default value for the matrix. For dense, this is undefined and will return Qnil. For list, it is user-defined.
443
+ * For yale, it's going to be some variation on zero, but may be Qfalse or Qnil.
444
+ */
445
+ static VALUE nm_default_value(VALUE self) {
446
+ switch(NM_STYPE(self)) {
447
+ case nm::YALE_STORE:
448
+ return nm_yale_default_value(self);
449
+ case nm::LIST_STORE:
450
+ return nm_list_default_value(self);
451
+ case nm::DENSE_STORE:
452
+ default:
453
+ return Qnil;
454
+ }
455
+ }
456
+
457
+
458
+ /*
459
+ * call-seq:
460
+ * each_with_indices -> Enumerator
461
+ *
462
+ * Iterate over all entries of any matrix in standard storage order (as with #each), and include the indices.
463
+ */
464
+ static VALUE nm_each_with_indices(VALUE nmatrix) {
465
+ volatile VALUE nm = nmatrix;
466
+
467
+ switch(NM_STYPE(nm)) {
468
+ case nm::YALE_STORE:
469
+ return nm_yale_each_with_indices(nm);
470
+ case nm::DENSE_STORE:
471
+ return nm_dense_each_with_indices(nm);
472
+ case nm::LIST_STORE:
473
+ return nm_list_each_with_indices(nm, false);
474
+ default:
475
+ rb_raise(nm_eDataTypeError, "Not a proper storage type");
476
+ }
477
+ }
478
+
479
+ /*
480
+ * call-seq:
481
+ * each_stored_with_indices -> Enumerator
482
+ *
483
+ * Iterate over the stored entries of any matrix. For dense and yale, this iterates over non-zero
484
+ * entries; for list, this iterates over non-default entries. Yields dim+1 values for each entry:
485
+ * i, j, ..., and the entry itself.
486
+ */
487
+ static VALUE nm_each_stored_with_indices(VALUE nmatrix) {
488
+ volatile VALUE nm = nmatrix;
489
+
490
+ switch(NM_STYPE(nm)) {
491
+ case nm::YALE_STORE:
492
+ return nm_yale_each_stored_with_indices(nm);
493
+ case nm::DENSE_STORE:
494
+ return nm_dense_each_with_indices(nm);
495
+ case nm::LIST_STORE:
496
+ return nm_list_each_with_indices(nm, true);
497
+ default:
498
+ rb_raise(nm_eDataTypeError, "Not a proper storage type");
499
+ }
500
+ }
501
+
502
+
503
+ /*
504
+ * call-seq:
505
+ * each_ordered_stored_with_indices -> Enumerator
506
+ *
507
+ * Very similar to #each_stored_with_indices. The key difference is that it enforces matrix ordering rather
508
+ * than storage ordering, which only matters if your matrix is Yale.
509
+ */
510
+ static VALUE nm_each_ordered_stored_with_indices(VALUE nmatrix) {
511
+ volatile VALUE nm = nmatrix;
512
+
513
+ switch(NM_STYPE(nm)) {
514
+ case nm::YALE_STORE:
515
+ return nm_yale_each_ordered_stored_with_indices(nm);
516
+ case nm::DENSE_STORE:
517
+ return nm_dense_each_with_indices(nm);
518
+ case nm::LIST_STORE:
519
+ return nm_list_each_with_indices(nm, true);
520
+ default:
521
+ rb_raise(nm_eDataTypeError, "Not a proper storage type");
522
+ }
523
+ }
524
+
525
+
526
+ /*
527
+ * Equality operator. Returns a single true or false value indicating whether
528
+ * the matrices are equivalent.
529
+ *
530
+ * For elementwise, use =~ instead.
531
+ *
532
+ * This method will raise an exception if dimensions do not match.
533
+ */
534
+ static VALUE nm_eqeq(VALUE left, VALUE right) {
535
+ NMATRIX *l, *r;
536
+
537
+ CheckNMatrixType(left);
538
+ CheckNMatrixType(right);
539
+
540
+ UnwrapNMatrix(left, l);
541
+ UnwrapNMatrix(right, r);
542
+
543
+ if (l->stype != r->stype)
544
+ rb_raise(rb_eNotImpError, "comparison between different matrix stypes not yet implemented");
545
+
546
+ bool result = false;
547
+
548
+ switch(l->stype) {
549
+ case nm::DENSE_STORE:
550
+ result = nm_dense_storage_eqeq(l->storage, r->storage);
551
+ break;
552
+ case nm::LIST_STORE:
553
+ result = nm_list_storage_eqeq(l->storage, r->storage);
554
+ break;
555
+ case nm::YALE_STORE:
556
+ result = nm_yale_storage_eqeq(l->storage, r->storage);
557
+ break;
558
+ }
559
+
560
+ return result ? Qtrue : Qfalse;
561
+ }
562
+
563
+ DEF_ELEMENTWISE_RUBY_ACCESSOR(ADD, add)
564
+ DEF_ELEMENTWISE_RUBY_ACCESSOR(SUB, subtract)
565
+ DEF_ELEMENTWISE_RUBY_ACCESSOR(MUL, multiply)
566
+ DEF_ELEMENTWISE_RUBY_ACCESSOR(DIV, divide)
567
+ DEF_ELEMENTWISE_RUBY_ACCESSOR(POW, power)
568
+ DEF_ELEMENTWISE_RUBY_ACCESSOR(MOD, mod)
569
+ DEF_ELEMENTWISE_RUBY_ACCESSOR(EQEQ, eqeq)
570
+ DEF_ELEMENTWISE_RUBY_ACCESSOR(NEQ, neq)
571
+ DEF_ELEMENTWISE_RUBY_ACCESSOR(LEQ, leq)
572
+ DEF_ELEMENTWISE_RUBY_ACCESSOR(GEQ, geq)
573
+ DEF_ELEMENTWISE_RUBY_ACCESSOR(LT, lt)
574
+ DEF_ELEMENTWISE_RUBY_ACCESSOR(GT, gt)
575
+
576
+ /*
577
+ * call-seq:
578
+ * hermitian? -> Boolean
579
+ *
580
+ * Is this matrix hermitian?
581
+ *
582
+ * Definition: http://en.wikipedia.org/wiki/Hermitian_matrix
583
+ *
584
+ * For non-complex matrices, this function should return the same result as symmetric?.
585
+ */
586
+ static VALUE nm_hermitian(VALUE self) {
587
+ return is_symmetric(self, true);
588
+ }
589
+
590
+
591
+ /*
592
+ * call-seq:
593
+ * complex_conjugate -> NMatrix
594
+ *
595
+ * Transform the matrix (in-place) to its complex conjugate. Only works on complex matrices.
596
+ *
597
+ * FIXME: For non-complex matrices, someone needs to implement a non-in-place complex conjugate (which doesn't use a bang).
598
+ * Bang should imply that no copy is being made, even temporarily.
599
+ */
600
+ static VALUE nm_complex_conjugate_bang(VALUE self) {
601
+ NMATRIX* m;
602
+ void* elem;
603
+ size_t size, p;
604
+
605
+ UnwrapNMatrix(self, m);
606
+
607
+ if (m->stype == nm::DENSE_STORE) {
608
+
609
+ size = nm_storage_count_max_elements(NM_STORAGE(self));
610
+ elem = NM_STORAGE_DENSE(self)->elements;
611
+
612
+ } else if (m->stype == nm::YALE_STORE) {
613
+
614
+ size = nm_yale_storage_get_size(NM_STORAGE_YALE(self));
615
+ elem = NM_STORAGE_YALE(self)->a;
616
+
617
+ } else {
618
+ rb_raise(rb_eNotImpError, "please cast to yale or dense (complex) first");
619
+ }
620
+
621
+ // Walk through and negate the imaginary component
622
+ if (NM_DTYPE(self) == nm::COMPLEX64) {
623
+
624
+ for (p = 0; p < size; ++p) {
625
+ reinterpret_cast<nm::Complex64*>(elem)[p].i = -reinterpret_cast<nm::Complex64*>(elem)[p].i;
626
+ }
627
+
628
+ } else if (NM_DTYPE(self) == nm::COMPLEX128) {
629
+
630
+ for (p = 0; p < size; ++p) {
631
+ reinterpret_cast<nm::Complex128*>(elem)[p].i = -reinterpret_cast<nm::Complex128*>(elem)[p].i;
632
+ }
633
+
634
+ } else {
635
+ rb_raise(nm_eDataTypeError, "can only calculate in-place complex conjugate on matrices of type :complex64 or :complex128");
636
+ }
637
+
638
+ return self;
639
+ }
640
+
641
+ /*
642
+ * Helper function for creating a matrix. You have to create the storage and pass it in, but you don't
643
+ * need to worry about deleting it.
644
+ */
645
+ NMATRIX* nm_create(nm::stype_t stype, STORAGE* storage) {
646
+ NMATRIX* mat = ALLOC(NMATRIX);
647
+
648
+ mat->stype = stype;
649
+ mat->storage = storage;
650
+
651
+ return mat;
652
+ }
653
+
654
+ /*
655
+ * @see nm_init
656
+ */
657
+ static VALUE nm_init_new_version(int argc, VALUE* argv, VALUE self) {
658
+ VALUE shape_ary, initial_ary, hash;
659
+ //VALUE shape_ary, default_val, capacity, initial_ary, dtype_sym, stype_sym;
660
+ // Mandatory args: shape, dtype, stype
661
+ // FIXME: This is the one line of code standing between Ruby 1.9.2 and 1.9.3.
662
+ #ifndef OLD_RB_SCAN_ARGS // Ruby 1.9.3 and higher
663
+ rb_scan_args(argc, argv, "11:", &shape_ary, &initial_ary, &hash); // &stype_sym, &dtype_sym, &default_val, &capacity);
664
+ #else // Ruby 1.9.2 and lower
665
+ if (argc == 3)
666
+ rb_scan_args(argc, argv, "12", &shape_ary, &initial_ary, &hash);
667
+ else if (argc == 2) {
668
+ VALUE unknown_arg;
669
+ rb_scan_args(argc, argv, "11", &shape_ary, &unknown_arg);
670
+ if (!NIL_P(unknown_arg) && TYPE(unknown_arg) == T_HASH) {
671
+ hash = unknown_arg;
672
+ initial_ary = Qnil;
673
+ } else {
674
+ initial_ary = unknown_arg;
675
+ hash = Qnil;
676
+ }
677
+ }
678
+ #endif
679
+
680
+ // Get the shape.
681
+ size_t dim;
682
+ size_t* shape = interpret_shape(shape_ary, &dim);
683
+ void* init;
684
+ void* v = NULL;
685
+ size_t v_size = 0;
686
+
687
+ nm::stype_t stype = nm::DENSE_STORE;
688
+ nm::dtype_t dtype = nm::RUBYOBJ;
689
+ VALUE dtype_sym = Qnil, stype_sym = Qnil, default_val_num = Qnil, capacity_num = Qnil;
690
+ size_t capacity = 0;
691
+ if (!NIL_P(hash)) {
692
+ dtype_sym = rb_hash_aref(hash, ID2SYM(nm_rb_dtype));
693
+ stype_sym = rb_hash_aref(hash, ID2SYM(nm_rb_stype));
694
+ capacity_num = rb_hash_aref(hash, ID2SYM(nm_rb_capacity));
695
+ default_val_num = rb_hash_aref(hash, ID2SYM(nm_rb_default));
696
+ }
697
+
698
+ // stype ||= :dense
699
+ stype = !NIL_P(stype_sym) ? nm_stype_from_rbsymbol(stype_sym) : nm::DENSE_STORE;
700
+
701
+ // dtype ||= h[:dtype] || guess_dtype(initial_ary) || :object
702
+ if (NIL_P(initial_ary) && NIL_P(dtype_sym))
703
+ dtype = nm::RUBYOBJ;
704
+ else if (NIL_P(dtype_sym))
705
+ dtype = nm_dtype_guess(initial_ary);
706
+ else
707
+ dtype = nm_dtype_from_rbsymbol(dtype_sym);
708
+
709
+ // if stype != :dense
710
+ // if initial_ary.nil?
711
+ // init = h[:default] || 0
712
+ // elsif initial_ary.is_a?(Array)
713
+ // init = initial_ary.size > 1 ? (h[:default] || 0) : initial_ary[0]
714
+ // else
715
+ // init = initial_ary # not an array, just a value
716
+ // end
717
+ // end
718
+ if (stype != nm::DENSE_STORE) {
719
+ if (!NIL_P(default_val_num))
720
+ init = rubyobj_to_cval(default_val_num, dtype);
721
+ else if (NIL_P(initial_ary))
722
+ init = NULL;
723
+ else if (TYPE(initial_ary) == T_ARRAY)
724
+ init = RARRAY_LEN(initial_ary) == 1 ? rubyobj_to_cval(rb_ary_entry(initial_ary, 0), dtype) : NULL;
725
+ else
726
+ init = rubyobj_to_cval(initial_ary, dtype);
727
+ }
728
+
729
+ // capacity = h[:capacity] || 0
730
+ if (stype == nm::YALE_STORE) {
731
+ if (!NIL_P(capacity_num)) capacity = FIX2INT(capacity_num);
732
+ }
733
+
734
+ if (!NIL_P(initial_ary)) {
735
+ v = interpret_initial_value(initial_ary, dtype);
736
+
737
+ if (TYPE(initial_ary) == T_ARRAY) v_size = RARRAY_LEN(initial_ary);
738
+ else v_size = 1;
739
+ }
740
+
741
+ // :object matrices MUST be initialized.
742
+ else if (stype == nm::DENSE_STORE && dtype == nm::RUBYOBJ) {
743
+ // Pretend [nil] was passed for RUBYOBJ.
744
+ v = ALLOC(VALUE);
745
+ *(VALUE*)v = Qnil;
746
+
747
+ v_size = 1;
748
+
749
+ }
750
+
751
+ NMATRIX* nmatrix;
752
+ UnwrapNMatrix(self, nmatrix);
753
+
754
+ nmatrix->stype = stype;
755
+
756
+ switch (stype) {
757
+ case nm::DENSE_STORE:
758
+ nmatrix->storage = (STORAGE*)nm_dense_storage_create(dtype, shape, dim, v, v_size);
759
+ break;
760
+
761
+ case nm::LIST_STORE:
762
+ nmatrix->storage = (STORAGE*)nm_list_storage_create(dtype, shape, dim, init);
763
+ break;
764
+
765
+ case nm::YALE_STORE:
766
+ nmatrix->storage = (STORAGE*)nm_yale_storage_create(dtype, shape, dim, capacity);
767
+ nm_yale_storage_init((YALE_STORAGE*)(nmatrix->storage), init);
768
+ break;
769
+ }
770
+
771
+ // If we're not creating a dense, and an initial array was provided, use that and multi-slice-set
772
+ // to set the contents of the matrix right now.
773
+ if (stype != nm::DENSE_STORE && v_size > 1) {
774
+ VALUE* slice_argv = ALLOCA_N(VALUE, dim);
775
+ size_t* tmp_shape = ALLOC_N(size_t, dim);
776
+ for (size_t m = 0; m < dim; ++m) {
777
+ slice_argv[m] = ID2SYM(nm_rb_mul); // :* -- full range
778
+ tmp_shape[m] = shape[m];
779
+ }
780
+
781
+ SLICE* slice = get_slice(dim, dim, slice_argv, shape);
782
+ // Create a temporary dense matrix and use it to do a slice assignment on self.
783
+ NMATRIX* tmp = nm_create(nm::DENSE_STORE, (STORAGE*)nm_dense_storage_create(dtype, tmp_shape, dim, v, v_size));
784
+ volatile VALUE rb_tmp = Data_Wrap_Struct(CLASS_OF(self), nm_mark, nm_delete, tmp);
785
+ if (stype == nm::YALE_STORE) nm_yale_storage_set(self, slice, rb_tmp);
786
+ else nm_list_storage_set(self, slice, rb_tmp);
787
+
788
+ free_slice(slice);
789
+
790
+ // We need to free v if it's not the same size as tmp -- because tmp will have made a copy instead.
791
+ if (nm_storage_count_max_elements(tmp->storage) != v_size)
792
+ xfree(v);
793
+
794
+ // 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.
795
+ }
796
+
797
+ return self;
798
+ }
799
+
800
+ /*
801
+ * call-seq:
802
+ * new(shape) -> NMatrix
803
+ * new(shape, initial_value) -> NMatrix
804
+ * new(shape, initial_array) -> NMatrix
805
+ * new(shape, initial_value, options) -> NMatrix
806
+ * new(shape, initial_array, options) -> NMatrix
807
+ *
808
+ * Create a new NMatrix.
809
+ *
810
+ * The only mandatory argument is shape, which may be a positive integer or an array of positive integers.
811
+ *
812
+ * It is recommended that you supply an initialization value or array of values. Without one, Yale and List matrices will
813
+ * be initialized to 0; and dense matrices will be undefined.
814
+ *
815
+ * Additional options may be provided using keyword arguments. The keywords are +:dtype, +:stype+, +:capacity+, and
816
+ * +:default+. Only Yale uses a capacity argument, which is used to reserve the initial size of its storage vectors.
817
+ * List and Yale both accept a default value (which itself defaults to 0). This default is taken from the initial value
818
+ * if such a value is given; it is more likely to be required when an initial array is provided.
819
+ *
820
+ * The storage type, or stype, is used to specify whether we want a +:dense+, +:list+, or +:yale+ matrix; dense is the
821
+ * default.
822
+ *
823
+ * The data type, or dtype, can be one of: :byte, :int8, :int16, :int32, :int64, :float32, :float64, :complex64,
824
+ * :complex128, :rational128, or :object. The constructor will attempt to guess it from the initial value/array/default
825
+ * provided, if any. Otherwise, the default is :object, which stores any type of Ruby object.
826
+ *
827
+ * In addition to the above, there is a legacy constructor from the alpha version. To use that version, you must be
828
+ * providing exactly four arguments. It is now deprecated.
829
+ *
830
+ * There is one additional constructor for advanced users, which takes seven arguments and is only for creating Yale
831
+ * matrices with known IA, JA, and A arrays. This is used primarily internally for IO, e.g., reading Matlab matrices,
832
+ * which are stored in old Yale (not our Yale) format. But be careful; there are no overflow warnings. All of these
833
+ * constructors are defined for power-users. Everyone else should probably resort to the shortcut functions defined in
834
+ * shortcuts.rb.
835
+ */
836
+ static VALUE nm_init(int argc, VALUE* argv, VALUE nm) {
837
+
838
+ if (argc <= 3) { // Call the new constructor unless all four arguments are given (or the 7-arg version is given)
839
+ return nm_init_new_version(argc, argv, nm);
840
+ }
841
+
842
+ /* First, determine stype (dense by default) */
843
+ nm::stype_t stype;
844
+ size_t offset = 0;
845
+
846
+ if (!SYMBOL_P(argv[0]) && TYPE(argv[0]) != T_STRING) {
847
+ stype = nm::DENSE_STORE;
848
+
849
+ } else {
850
+ // 0: String or Symbol
851
+ stype = interpret_stype(argv[0]);
852
+ offset = 1;
853
+ }
854
+
855
+ // If there are 7 arguments and Yale, refer to a different init function with fewer sanity checks.
856
+ if (argc == 7) {
857
+ if (stype == nm::YALE_STORE) {
858
+ return nm_init_yale_from_old_yale(argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], nm);
859
+
860
+ } else {
861
+ rb_raise(rb_eArgError, "Expected 2-4 arguments (or 7 for internal Yale creation)");
862
+ }
863
+ }
864
+
865
+ // 1: Array or Fixnum
866
+ size_t dim;
867
+ size_t* shape = interpret_shape(argv[offset], &dim);
868
+
869
+ // 2-3: dtype
870
+ nm::dtype_t dtype = interpret_dtype(argc-1-offset, argv+offset+1, stype);
871
+
872
+ size_t init_cap = 0, init_val_len = 0;
873
+ void* init_val = NULL;
874
+ if (!SYMBOL_P(argv[1+offset]) || TYPE(argv[1+offset]) == T_ARRAY) {
875
+ // Initial value provided (could also be initial capacity, if yale).
876
+
877
+ if (stype == nm::YALE_STORE && NM_RUBYVAL_IS_NUMERIC(argv[1+offset])) {
878
+ init_cap = FIX2UINT(argv[1+offset]);
879
+
880
+ } else {
881
+ // 4: initial value / dtype
882
+ init_val = interpret_initial_value(argv[1+offset], dtype);
883
+
884
+ if (TYPE(argv[1+offset]) == T_ARRAY) init_val_len = RARRAY_LEN(argv[1+offset]);
885
+ else init_val_len = 1;
886
+ }
887
+
888
+ } else {
889
+ // DType is RUBYOBJ.
890
+
891
+ if (stype == nm::DENSE_STORE) {
892
+ /*
893
+ * No need to initialize dense with any kind of default value unless it's
894
+ * an RUBYOBJ matrix.
895
+ */
896
+ if (dtype == nm::RUBYOBJ) {
897
+ // Pretend [nil] was passed for RUBYOBJ.
898
+ init_val = ALLOC(VALUE);
899
+ *(VALUE*)init_val = Qnil;
900
+
901
+ init_val_len = 1;
902
+
903
+ } else {
904
+ init_val = NULL;
905
+ }
906
+ } else if (stype == nm::LIST_STORE) {
907
+ init_val = ALLOC_N(char, DTYPE_SIZES[dtype]);
908
+ std::memset(init_val, 0, DTYPE_SIZES[dtype]);
909
+ }
910
+ }
911
+
912
+ // TODO: Update to allow an array as the initial value.
913
+ NMATRIX* nmatrix;
914
+ UnwrapNMatrix(nm, nmatrix);
915
+
916
+ nmatrix->stype = stype;
917
+
918
+ switch (stype) {
919
+ case nm::DENSE_STORE:
920
+ nmatrix->storage = (STORAGE*)nm_dense_storage_create(dtype, shape, dim, init_val, init_val_len);
921
+ break;
922
+
923
+ case nm::LIST_STORE:
924
+ nmatrix->storage = (STORAGE*)nm_list_storage_create(dtype, shape, dim, init_val);
925
+ break;
926
+
927
+ case nm::YALE_STORE:
928
+ nmatrix->storage = (STORAGE*)nm_yale_storage_create(dtype, shape, dim, init_cap);
929
+ nm_yale_storage_init((YALE_STORAGE*)(nmatrix->storage), NULL);
930
+ break;
931
+ }
932
+
933
+ return nm;
934
+ }
935
+
936
+
937
+ /*
938
+ * Helper for nm_cast which uses the C types instead of the Ruby objects. Called by nm_cast.
939
+ */
940
+ NMATRIX* nm_cast_with_ctype_args(NMATRIX* self, nm::stype_t new_stype, nm::dtype_t new_dtype, void* init_ptr) {
941
+ NMATRIX* lhs = ALLOC(NMATRIX);
942
+ lhs->stype = new_stype;
943
+
944
+ // Copy the storage
945
+ CAST_TABLE(cast_copy);
946
+ lhs->storage = cast_copy[lhs->stype][self->stype](self->storage, new_dtype, init_ptr);
947
+
948
+ return lhs;
949
+ }
950
+
951
+
952
+ /*
953
+ * call-seq:
954
+ * cast_full(stype) -> NMatrix
955
+ * cast_full(stype, dtype, sparse_basis) -> NMatrix
956
+ *
957
+ * Copy constructor for changing dtypes and stypes.
958
+ */
959
+ VALUE nm_cast(VALUE self, VALUE new_stype_symbol, VALUE new_dtype_symbol, VALUE init) {
960
+ nm::dtype_t new_dtype = nm_dtype_from_rbsymbol(new_dtype_symbol);
961
+ nm::stype_t new_stype = nm_stype_from_rbsymbol(new_stype_symbol);
962
+
963
+ CheckNMatrixType(self);
964
+ NMATRIX *rhs;
965
+
966
+ UnwrapNMatrix( self, rhs );
967
+
968
+ void* init_ptr = ALLOCA_N(char, DTYPE_SIZES[new_dtype]);
969
+ rubyval_to_cval(init, new_dtype, init_ptr);
970
+
971
+ return Data_Wrap_Struct(CLASS_OF(self), nm_mark, nm_delete, nm_cast_with_ctype_args(rhs, new_stype, new_dtype, init_ptr));
972
+ }
973
+
974
+ /*
975
+ * Copy constructor for transposing.
976
+ */
977
+ static VALUE nm_init_transposed(VALUE self) {
978
+ static STORAGE* (*storage_copy_transposed[nm::NUM_STYPES])(const STORAGE* rhs_base) = {
979
+ nm_dense_storage_copy_transposed,
980
+ nm_list_storage_copy_transposed,
981
+ nm_yale_storage_copy_transposed
982
+ };
983
+
984
+ NMATRIX* lhs = nm_create( NM_STYPE(self),
985
+ storage_copy_transposed[NM_STYPE(self)]( NM_STORAGE(self) )
986
+ );
987
+
988
+ return Data_Wrap_Struct(CLASS_OF(self), nm_mark, nm_delete, lhs);
989
+ }
990
+
991
+ /*
992
+ * Copy constructor for no change of dtype or stype (used for #initialize_copy hook).
993
+ */
994
+ static VALUE nm_init_copy(VALUE copy, VALUE original) {
995
+ NMATRIX *lhs, *rhs;
996
+
997
+ CheckNMatrixType(original);
998
+
999
+ if (copy == original) return copy;
1000
+
1001
+ UnwrapNMatrix( original, rhs );
1002
+ UnwrapNMatrix( copy, lhs );
1003
+
1004
+ lhs->stype = rhs->stype;
1005
+
1006
+ // Copy the storage
1007
+ CAST_TABLE(ttable);
1008
+ lhs->storage = ttable[lhs->stype][rhs->stype](rhs->storage, rhs->storage->dtype, NULL);
1009
+
1010
+ return copy;
1011
+ }
1012
+
1013
+ /*
1014
+ * Get major, minor, and release components of NMatrix::VERSION. Store in function parameters.
1015
+ */
1016
+ static void get_version_info(uint16_t& major, uint16_t& minor, uint16_t& release) {
1017
+ // Get VERSION and split it on periods. Result is an Array.
1018
+ VALUE version = rb_funcall(rb_const_get(cNMatrix, rb_intern("VERSION")), rb_intern("split"), 1, rb_str_new_cstr("."));
1019
+ VALUE* ary = RARRAY_PTR(version); // major, minor, and release
1020
+
1021
+ // Convert each to an integer
1022
+ VALUE maj = rb_funcall(ary[0], rb_intern("to_i"), 0);
1023
+ VALUE min = rb_funcall(ary[1], rb_intern("to_i"), 0);
1024
+ VALUE rel = rb_funcall(ary[2], rb_intern("to_i"), 0);
1025
+
1026
+ major = static_cast<uint16_t>(nm::RubyObject(maj));
1027
+ minor = static_cast<uint16_t>(nm::RubyObject(min));
1028
+ release = static_cast<uint16_t>(nm::RubyObject(rel));
1029
+ }
1030
+
1031
+
1032
+ /*
1033
+ * Interpret the NMatrix::write symmetry argument (which should be nil or a symbol). Return a symm_t (enum).
1034
+ */
1035
+ static nm::symm_t interpret_symm(VALUE symm) {
1036
+ if (symm == Qnil) return nm::NONSYMM;
1037
+
1038
+ ID rb_symm = rb_intern("symmetric"),
1039
+ rb_skew = rb_intern("skew"),
1040
+ rb_herm = rb_intern("hermitian");
1041
+ // nm_rb_upper, nm_rb_lower already set
1042
+
1043
+ ID symm_id = rb_to_id(symm);
1044
+
1045
+ if (symm_id == rb_symm) return nm::SYMM;
1046
+ else if (symm_id == rb_skew) return nm::SKEW;
1047
+ else if (symm_id == rb_herm) return nm::HERM;
1048
+ else if (symm_id == nm_rb_upper) return nm::UPPER;
1049
+ else if (symm_id == nm_rb_lower) return nm::LOWER;
1050
+ else rb_raise(rb_eArgError, "unrecognized symmetry argument");
1051
+
1052
+ return nm::NONSYMM;
1053
+ }
1054
+
1055
+
1056
+
1057
+ void read_padded_shape(std::ifstream& f, size_t dim, size_t* shape) {
1058
+ nm::read_padded_shape(f, dim, shape);
1059
+ }
1060
+
1061
+
1062
+ void write_padded_shape(std::ofstream& f, size_t dim, size_t* shape) {
1063
+ nm::write_padded_shape(f, dim, shape);
1064
+ }
1065
+
1066
+
1067
+ void read_padded_yale_elements(std::ifstream& f, YALE_STORAGE* storage, size_t length, nm::symm_t symm, nm::dtype_t dtype) {
1068
+ NAMED_DTYPE_TEMPLATE_TABLE_NO_ROBJ(ttable, nm::read_padded_yale_elements, void, std::ifstream&, YALE_STORAGE*, size_t, nm::symm_t)
1069
+
1070
+ ttable[dtype](f, storage, length, symm);
1071
+ }
1072
+
1073
+
1074
+ void write_padded_yale_elements(std::ofstream& f, YALE_STORAGE* storage, size_t length, nm::symm_t symm, nm::dtype_t dtype) {
1075
+ NAMED_DTYPE_TEMPLATE_TABLE_NO_ROBJ(ttable, nm::write_padded_yale_elements, void, std::ofstream& f, YALE_STORAGE*, size_t, nm::symm_t)
1076
+
1077
+ ttable[dtype](f, storage, length, symm);
1078
+ }
1079
+
1080
+
1081
+ void read_padded_dense_elements(std::ifstream& f, DENSE_STORAGE* storage, nm::symm_t symm, nm::dtype_t dtype) {
1082
+ NAMED_DTYPE_TEMPLATE_TABLE_NO_ROBJ(ttable, nm::read_padded_dense_elements, void, std::ifstream&, DENSE_STORAGE*, nm::symm_t)
1083
+
1084
+ ttable[dtype](f, storage, symm);
1085
+ }
1086
+
1087
+
1088
+ void write_padded_dense_elements(std::ofstream& f, DENSE_STORAGE* storage, nm::symm_t symm, nm::dtype_t dtype) {
1089
+ NAMED_DTYPE_TEMPLATE_TABLE_NO_ROBJ(ttable, nm::write_padded_dense_elements, void, std::ofstream& f, DENSE_STORAGE*, nm::symm_t)
1090
+
1091
+ ttable[dtype](f, storage, symm);
1092
+ }
1093
+
1094
+
1095
+ /*
1096
+ * Helper function to get exceptions in the module Errno (e.g., ENOENT). Example:
1097
+ *
1098
+ * rb_raise(rb_get_errno_exc("ENOENT"), RSTRING_PTR(filename));
1099
+ */
1100
+ static VALUE rb_get_errno_exc(const char* which) {
1101
+ return rb_const_get(rb_const_get(rb_cObject, rb_intern("Errno")), rb_intern(which));
1102
+ }
1103
+
1104
+
1105
+
1106
+ /*
1107
+ * Binary file writer for NMatrix standard format. file should be a path, which we aren't going to
1108
+ * check very carefully (in other words, this function should generally be called from a Ruby
1109
+ * helper method). Function also takes a symmetry argument, which allows us to specify that we only want to
1110
+ * save the upper triangular portion of the matrix (or if the matrix is a lower triangular matrix, only
1111
+ * the lower triangular portion). nil means regular storage.
1112
+ */
1113
+ static VALUE nm_write(int argc, VALUE* argv, VALUE self) {
1114
+ using std::ofstream;
1115
+
1116
+ if (argc < 1 || argc > 2) {
1117
+ rb_raise(rb_eArgError, "Expected one or two arguments");
1118
+ }
1119
+ VALUE file = argv[0],
1120
+ symm = argc == 1 ? Qnil : argv[1];
1121
+
1122
+ NMATRIX* nmatrix;
1123
+ UnwrapNMatrix( self, nmatrix );
1124
+
1125
+ nm::symm_t symm_ = interpret_symm(symm);
1126
+
1127
+ if (nmatrix->storage->dtype == nm::RUBYOBJ) {
1128
+ rb_raise(rb_eNotImpError, "Ruby Object writing is not implemented yet");
1129
+ }
1130
+
1131
+ // Get the dtype, stype, itype, and symm and ensure they're the correct number of bytes.
1132
+ uint8_t st = static_cast<uint8_t>(nmatrix->stype),
1133
+ dt = static_cast<uint8_t>(nmatrix->storage->dtype),
1134
+ sm = static_cast<uint8_t>(symm_);
1135
+ uint16_t dim = nmatrix->storage->dim;
1136
+
1137
+ //FIXME: Cast the matrix to the smallest possible index type. Write that in the place of IType.
1138
+
1139
+ // Check arguments before starting to write.
1140
+ if (nmatrix->stype == nm::LIST_STORE) rb_raise(nm_eStorageTypeError, "cannot save list matrix; cast to yale or dense first");
1141
+ if (symm_ != nm::NONSYMM) {
1142
+ if (dim != 2) rb_raise(rb_eArgError, "symmetry/triangularity not defined for a non-2D matrix");
1143
+ if (nmatrix->storage->shape[0] != nmatrix->storage->shape[1])
1144
+ rb_raise(rb_eArgError, "symmetry/triangularity not defined for a non-square matrix");
1145
+ if (symm_ == nm::HERM &&
1146
+ dt != static_cast<uint8_t>(nm::COMPLEX64) && dt != static_cast<uint8_t>(nm::COMPLEX128) && dt != static_cast<uint8_t>(nm::RUBYOBJ))
1147
+ rb_raise(rb_eArgError, "cannot save a non-complex matrix as hermitian");
1148
+ }
1149
+
1150
+ ofstream f(RSTRING_PTR(file), std::ios::out | std::ios::binary);
1151
+
1152
+ // Get the NMatrix version information.
1153
+ uint16_t major, minor, release, null16 = 0;
1154
+ get_version_info(major, minor, release);
1155
+
1156
+ // WRITE FIRST 64-BIT BLOCK
1157
+ f.write(reinterpret_cast<const char*>(&major), sizeof(uint16_t));
1158
+ f.write(reinterpret_cast<const char*>(&minor), sizeof(uint16_t));
1159
+ f.write(reinterpret_cast<const char*>(&release), sizeof(uint16_t));
1160
+ f.write(reinterpret_cast<const char*>(&null16), sizeof(uint16_t));
1161
+
1162
+ uint8_t ZERO = 0;
1163
+ // WRITE SECOND 64-BIT BLOCK
1164
+ f.write(reinterpret_cast<const char*>(&dt), sizeof(uint8_t));
1165
+ f.write(reinterpret_cast<const char*>(&st), sizeof(uint8_t));
1166
+ f.write(reinterpret_cast<const char*>(&ZERO),sizeof(uint8_t));
1167
+ f.write(reinterpret_cast<const char*>(&sm), sizeof(uint8_t));
1168
+ f.write(reinterpret_cast<const char*>(&null16), sizeof(uint16_t));
1169
+ f.write(reinterpret_cast<const char*>(&dim), sizeof(uint16_t));
1170
+
1171
+ // Write shape (in 64-bit blocks)
1172
+ write_padded_shape(f, nmatrix->storage->dim, nmatrix->storage->shape);
1173
+
1174
+ if (nmatrix->stype == nm::DENSE_STORE) {
1175
+ write_padded_dense_elements(f, reinterpret_cast<DENSE_STORAGE*>(nmatrix->storage), symm_, nmatrix->storage->dtype);
1176
+ } else if (nmatrix->stype == nm::YALE_STORE) {
1177
+ YALE_STORAGE* s = reinterpret_cast<YALE_STORAGE*>(nmatrix->storage);
1178
+ uint32_t ndnz = s->ndnz,
1179
+ length = nm_yale_storage_get_size(s);
1180
+ f.write(reinterpret_cast<const char*>(&ndnz), sizeof(uint32_t));
1181
+ f.write(reinterpret_cast<const char*>(&length), sizeof(uint32_t));
1182
+
1183
+ write_padded_yale_elements(f, s, length, symm_, s->dtype);
1184
+ }
1185
+
1186
+ f.close();
1187
+
1188
+ return Qtrue;
1189
+ }
1190
+
1191
+
1192
+ /*
1193
+ * Binary file reader for NMatrix standard format. file should be a path, which we aren't going to
1194
+ * check very carefully (in other words, this function should generally be called from a Ruby
1195
+ * helper method).
1196
+ *
1197
+ * Note that currently, this function will by default refuse to read files that are newer than
1198
+ * your version of NMatrix. To force an override, set the second argument to anything other than nil.
1199
+ *
1200
+ * Returns an NMatrix Ruby object.
1201
+ */
1202
+ static VALUE nm_read(int argc, VALUE* argv, VALUE self) {
1203
+ using std::ifstream;
1204
+
1205
+ VALUE file, force_;
1206
+
1207
+ // Read the arguments
1208
+ rb_scan_args(argc, argv, "11", &file, &force_);
1209
+ bool force = (force_ != Qnil && force_ != Qfalse);
1210
+
1211
+
1212
+ if (!RB_FILE_EXISTS(file)) { // FIXME: Errno::ENOENT
1213
+ rb_raise(rb_get_errno_exc("ENOENT"), "%s", RSTRING_PTR(file));
1214
+ }
1215
+
1216
+ // Open a file stream
1217
+ ifstream f(RSTRING_PTR(file), std::ios::in | std::ios::binary);
1218
+
1219
+ uint16_t major, minor, release;
1220
+ get_version_info(major, minor, release); // compare to NMatrix version
1221
+
1222
+ uint16_t fmajor, fminor, frelease, null16;
1223
+
1224
+ // READ FIRST 64-BIT BLOCK
1225
+ f.read(reinterpret_cast<char*>(&fmajor), sizeof(uint16_t));
1226
+ f.read(reinterpret_cast<char*>(&fminor), sizeof(uint16_t));
1227
+ f.read(reinterpret_cast<char*>(&frelease), sizeof(uint16_t));
1228
+ f.read(reinterpret_cast<char*>(&null16), sizeof(uint16_t));
1229
+
1230
+ int ver = major * 10000 + minor * 100 + release,
1231
+ fver = fmajor * 10000 + fminor * 100 + release;
1232
+ if (fver > ver && force == false) {
1233
+ rb_raise(rb_eIOError, "File was created in newer version of NMatrix than current");
1234
+ }
1235
+ if (null16 != 0) fprintf(stderr, "Warning: Expected zero padding was not zero\n");
1236
+
1237
+ uint8_t dt, st, it, sm;
1238
+ uint16_t dim;
1239
+
1240
+ // READ SECOND 64-BIT BLOCK
1241
+ f.read(reinterpret_cast<char*>(&dt), sizeof(uint8_t));
1242
+ f.read(reinterpret_cast<char*>(&st), sizeof(uint8_t));
1243
+ f.read(reinterpret_cast<char*>(&it), sizeof(uint8_t)); // FIXME: should tell how few bytes indices are stored as
1244
+ f.read(reinterpret_cast<char*>(&sm), sizeof(uint8_t));
1245
+ f.read(reinterpret_cast<char*>(&null16), sizeof(uint16_t));
1246
+ f.read(reinterpret_cast<char*>(&dim), sizeof(uint16_t));
1247
+
1248
+ if (null16 != 0) fprintf(stderr, "Warning: Expected zero padding was not zero\n");
1249
+ nm::stype_t stype = static_cast<nm::stype_t>(st);
1250
+ nm::dtype_t dtype = static_cast<nm::dtype_t>(dt);
1251
+ nm::symm_t symm = static_cast<nm::symm_t>(sm);
1252
+ //nm::itype_t itype = static_cast<nm::itype_t>(it);
1253
+
1254
+ // READ NEXT FEW 64-BIT BLOCKS
1255
+ size_t* shape = ALLOC_N(size_t, dim);
1256
+ read_padded_shape(f, dim, shape);
1257
+
1258
+ STORAGE* s;
1259
+ if (stype == nm::DENSE_STORE) {
1260
+ s = nm_dense_storage_create(dtype, shape, dim, NULL, 0);
1261
+
1262
+ read_padded_dense_elements(f, reinterpret_cast<DENSE_STORAGE*>(s), symm, dtype);
1263
+
1264
+ } else if (stype == nm::YALE_STORE) {
1265
+ uint32_t ndnz, length;
1266
+
1267
+ // READ YALE-SPECIFIC 64-BIT BLOCK
1268
+ f.read(reinterpret_cast<char*>(&ndnz), sizeof(uint32_t));
1269
+ f.read(reinterpret_cast<char*>(&length), sizeof(uint32_t));
1270
+
1271
+ s = nm_yale_storage_create(dtype, shape, dim, length); // set length as init capacity
1272
+
1273
+ read_padded_yale_elements(f, reinterpret_cast<YALE_STORAGE*>(s), length, symm, dtype);
1274
+ } else {
1275
+ rb_raise(nm_eStorageTypeError, "please convert to yale or dense before saving");
1276
+ }
1277
+
1278
+ NMATRIX* nm = nm_create(stype, s);
1279
+
1280
+ // Return the appropriate matrix object (Ruby VALUE)
1281
+ // FIXME: This should probably return CLASS_OF(self) instead of cNMatrix, but I don't know how that works for
1282
+ // FIXME: class methods.
1283
+ switch(stype) {
1284
+ case nm::DENSE_STORE:
1285
+ case nm::YALE_STORE:
1286
+ return Data_Wrap_Struct(cNMatrix, nm_mark, nm_delete, nm);
1287
+ default: // this case never occurs (due to earlier rb_raise)
1288
+ return Qnil;
1289
+ }
1290
+
1291
+ }
1292
+
1293
+
1294
+
1295
+ /*
1296
+ * Create a new NMatrix helper for handling internal ia, ja, and a arguments.
1297
+ *
1298
+ * This constructor is only called by Ruby code, so we can skip most of the
1299
+ * checks.
1300
+ */
1301
+ static VALUE nm_init_yale_from_old_yale(VALUE shape, VALUE dtype, VALUE ia, VALUE ja, VALUE a, VALUE from_dtype, VALUE nm) {
1302
+ size_t dim = 2;
1303
+ size_t* shape_ = interpret_shape(shape, &dim);
1304
+ nm::dtype_t dtype_ = nm_dtype_from_rbsymbol(dtype);
1305
+ char *ia_ = RSTRING_PTR(ia),
1306
+ *ja_ = RSTRING_PTR(ja),
1307
+ *a_ = RSTRING_PTR(a);
1308
+ nm::dtype_t from_dtype_ = nm_dtype_from_rbsymbol(from_dtype);
1309
+ NMATRIX* nmatrix;
1310
+
1311
+ UnwrapNMatrix( nm, nmatrix );
1312
+
1313
+ nmatrix->stype = nm::YALE_STORE;
1314
+ nmatrix->storage = (STORAGE*)nm_yale_storage_create_from_old_yale(dtype_, shape_, ia_, ja_, a_, from_dtype_);
1315
+
1316
+ return nm;
1317
+ }
1318
+
1319
+ /*
1320
+ * Check to determine whether matrix is a reference to another matrix.
1321
+ */
1322
+ static VALUE nm_is_ref(VALUE self) {
1323
+ if (NM_SRC(self) == NM_STORAGE(self)) return Qfalse;
1324
+ else return Qtrue;
1325
+ }
1326
+
1327
+ /*
1328
+ * call-seq:
1329
+ * slice -> ...
1330
+ *
1331
+ * Access the contents of an NMatrix at given coordinates, using copying.
1332
+ *
1333
+ * n.slice(3,3) # => 5.0
1334
+ * n.slice(0..1,0..1) #=> matrix [2,2]
1335
+ *
1336
+ */
1337
+ static VALUE nm_mget(int argc, VALUE* argv, VALUE self) {
1338
+ static void* (*ttable[nm::NUM_STYPES])(const STORAGE*, SLICE*) = {
1339
+ nm_dense_storage_get,
1340
+ nm_list_storage_get,
1341
+ nm_yale_storage_get
1342
+ };
1343
+ return nm_xslice(argc, argv, ttable[NM_STYPE(self)], nm_delete, self);
1344
+ }
1345
+
1346
+ /*
1347
+ * call-seq:
1348
+ * matrix[indices] -> ...
1349
+ *
1350
+ * Access the contents of an NMatrix at given coordinates by reference.
1351
+ *
1352
+ * n[3,3] # => 5.0
1353
+ * n[0..1,0..1] #=> matrix [2,2]
1354
+ *
1355
+ */
1356
+ static VALUE nm_mref(int argc, VALUE* argv, VALUE self) {
1357
+ static void* (*ttable[nm::NUM_STYPES])(const STORAGE*, SLICE*) = {
1358
+ nm_dense_storage_ref,
1359
+ nm_list_storage_ref,
1360
+ nm_yale_storage_ref
1361
+ };
1362
+ return nm_xslice(argc, argv, ttable[NM_STYPE(self)], nm_delete_ref, self);
1363
+ }
1364
+
1365
+ /*
1366
+ * Modify the contents of an NMatrix in the given cell
1367
+ *
1368
+ * n[3,3] = 5.0
1369
+ *
1370
+ * Also returns the new contents, so you can chain:
1371
+ *
1372
+ * n[3,3] = n[2,3] = 5.0
1373
+ */
1374
+ static VALUE nm_mset(int argc, VALUE* argv, VALUE self) {
1375
+ size_t dim = NM_DIM(self); // last arg is the value
1376
+
1377
+ if ((size_t)(argc) > NM_DIM(self)+1) {
1378
+ rb_raise(rb_eArgError, "wrong number of arguments (%d for %u)", argc, effective_dim(NM_STORAGE(self))+1);
1379
+ } else {
1380
+ SLICE* slice = get_slice(dim, argc-1, argv, NM_STORAGE(self)->shape);
1381
+
1382
+ static void (*ttable[nm::NUM_STYPES])(VALUE, SLICE*, VALUE) = {
1383
+ nm_dense_storage_set,
1384
+ nm_list_storage_set,
1385
+ nm_yale_storage_set
1386
+ };
1387
+
1388
+ ttable[NM_STYPE(self)](self, slice, argv[argc-1]);
1389
+
1390
+ free_slice(slice);
1391
+
1392
+ return argv[argc-1];
1393
+ }
1394
+ return Qnil;
1395
+ }
1396
+
1397
+ /*
1398
+ * Matrix multiply (dot product): against another matrix or a vector.
1399
+ *
1400
+ * For elementwise, use * instead.
1401
+ *
1402
+ * The two matrices must be of the same stype (for now). If dtype differs, an upcast will occur.
1403
+ */
1404
+ static VALUE nm_multiply(VALUE left_v, VALUE right_v) {
1405
+ NMATRIX *left, *right;
1406
+
1407
+ UnwrapNMatrix( left_v, left );
1408
+
1409
+ if (NM_RUBYVAL_IS_NUMERIC(right_v))
1410
+ return matrix_multiply_scalar(left, right_v);
1411
+
1412
+ else if (TYPE(right_v) == T_ARRAY)
1413
+ rb_raise(rb_eNotImpError, "please convert array to nx1 or 1xn NMatrix first");
1414
+
1415
+ else { // both are matrices (probably)
1416
+ CheckNMatrixType(right_v);
1417
+ UnwrapNMatrix( right_v, right );
1418
+
1419
+ if (left->storage->shape[1] != right->storage->shape[0])
1420
+ rb_raise(rb_eArgError, "incompatible dimensions");
1421
+
1422
+ if (left->stype != right->stype)
1423
+ rb_raise(rb_eNotImpError, "matrices must have same stype");
1424
+
1425
+ return matrix_multiply(left, right);
1426
+
1427
+ }
1428
+
1429
+ return Qnil;
1430
+ }
1431
+
1432
+
1433
+ /*
1434
+ * call-seq:
1435
+ * dim -> Integer
1436
+ *
1437
+ * Get the number of dimensions of a matrix.
1438
+ *
1439
+ * In other words, if you set your matrix to be 3x4, the dim is 2. If the
1440
+ * matrix was initialized as 3x4x3, the dim is 3.
1441
+ *
1442
+ * Use #effective_dim to get the dimension of an NMatrix which acts as a vector (e.g., a column or row).
1443
+ */
1444
+ static VALUE nm_dim(VALUE self) {
1445
+ return INT2FIX(NM_STORAGE(self)->dim);
1446
+ }
1447
+
1448
+ /*
1449
+ * call-seq:
1450
+ * shape -> Array
1451
+ *
1452
+ * Get the shape (dimensions) of a matrix.
1453
+ */
1454
+ static VALUE nm_shape(VALUE self) {
1455
+ STORAGE* s = NM_STORAGE(self);
1456
+
1457
+ // Copy elements into a VALUE array and then use those to create a Ruby array with rb_ary_new4.
1458
+ VALUE* shape = ALLOCA_N(VALUE, s->dim);
1459
+ for (size_t index = 0; index < s->dim; ++index)
1460
+ shape[index] = INT2FIX(s->shape[index]);
1461
+
1462
+ return rb_ary_new4(s->dim, shape);
1463
+ }
1464
+
1465
+
1466
+ /*
1467
+ * call-seq:
1468
+ * offset -> Array
1469
+ *
1470
+ * Get the offset (slice position) of a matrix. Typically all zeros, unless you have a reference slice.
1471
+ */
1472
+ static VALUE nm_offset(VALUE self) {
1473
+ STORAGE* s = NM_STORAGE(self);
1474
+
1475
+ // Copy elements into a VALUE array and then use those to create a Ruby array with rb_ary_new4.
1476
+ VALUE* offset = ALLOCA_N(VALUE, s->dim);
1477
+ for (size_t index = 0; index < s->dim; ++index)
1478
+ offset[index] = INT2FIX(s->offset[index]);
1479
+
1480
+ return rb_ary_new4(s->dim, offset);
1481
+ }
1482
+
1483
+
1484
+ /*
1485
+ * call-seq:
1486
+ * supershape -> Array
1487
+ *
1488
+ * Get the shape of a slice's parent.
1489
+ */
1490
+ static VALUE nm_supershape(VALUE self) {
1491
+
1492
+ STORAGE* s = NM_STORAGE(self);
1493
+ if (s->src == s) return nm_shape(self); // easy case (not a slice)
1494
+ else s = s->src;
1495
+
1496
+ VALUE* shape = ALLOCA_N(VALUE, s->dim);
1497
+ for (size_t index = 0; index < s->dim; ++index)
1498
+ shape[index] = INT2FIX(s->shape[index]);
1499
+
1500
+ return rb_ary_new4(s->dim, shape);
1501
+ }
1502
+
1503
+ /*
1504
+ * call-seq:
1505
+ * stype -> Symbol
1506
+ *
1507
+ * Get the storage type (stype) of a matrix, e.g., :yale, :dense, or :list.
1508
+ */
1509
+ static VALUE nm_stype(VALUE self) {
1510
+ ID stype = rb_intern(STYPE_NAMES[NM_STYPE(self)]);
1511
+ return ID2SYM(stype);
1512
+ }
1513
+
1514
+ /*
1515
+ * call-seq:
1516
+ * symmetric? -> Boolean
1517
+ *
1518
+ * Is this matrix symmetric?
1519
+ */
1520
+ static VALUE nm_symmetric(VALUE self) {
1521
+ return is_symmetric(self, false);
1522
+ }
1523
+
1524
+
1525
+ /*
1526
+ * Gets the dimension of a matrix which might be a vector (have one or more shape components of size 1).
1527
+ */
1528
+ static size_t effective_dim(STORAGE* s) {
1529
+ size_t d = 0;
1530
+ for (size_t i = 0; i < s->dim; ++i) {
1531
+ if (s->shape[i] != 1) d++;
1532
+ }
1533
+ return d;
1534
+ }
1535
+
1536
+
1537
+ /*
1538
+ * call-seq:
1539
+ * effective_dim -> Fixnum
1540
+ *
1541
+ * Returns the number of dimensions that don't have length 1. Guaranteed to be less than or equal to #dim.
1542
+ */
1543
+ static VALUE nm_effective_dim(VALUE self) {
1544
+ return INT2FIX(effective_dim(NM_STORAGE(self)));
1545
+ }
1546
+
1547
+
1548
+ /*
1549
+ * Get a slice of an NMatrix.
1550
+ */
1551
+ static VALUE nm_xslice(int argc, VALUE* argv, void* (*slice_func)(const STORAGE*, SLICE*), void (*delete_func)(NMATRIX*), VALUE self) {
1552
+ VALUE result = Qnil;
1553
+ STORAGE* s = NM_STORAGE(self);
1554
+
1555
+ if (NM_DIM(self) < (size_t)(argc)) {
1556
+ rb_raise(rb_eArgError, "wrong number of arguments (%d for %u)", argc, effective_dim(s));
1557
+ } else {
1558
+ SLICE* slice = get_slice(NM_DIM(self), argc, argv, s->shape);
1559
+
1560
+ if (slice->single) {
1561
+ static void* (*ttable[nm::NUM_STYPES])(const STORAGE*, SLICE*) = {
1562
+ nm_dense_storage_ref,
1563
+ nm_list_storage_ref,
1564
+ nm_yale_storage_ref
1565
+ };
1566
+
1567
+ if (NM_DTYPE(self) == nm::RUBYOBJ) result = *reinterpret_cast<VALUE*>( ttable[NM_STYPE(self)](s, slice) );
1568
+ else result = rubyobj_from_cval( ttable[NM_STYPE(self)](s, slice), NM_DTYPE(self) ).rval;
1569
+
1570
+ } else {
1571
+
1572
+ NMATRIX* mat = ALLOC(NMATRIX);
1573
+ mat->stype = NM_STYPE(self);
1574
+ mat->storage = (STORAGE*)((*slice_func)( s, slice ));
1575
+
1576
+ result = Data_Wrap_Struct(CLASS_OF(self), nm_mark, delete_func, mat);
1577
+ }
1578
+
1579
+ free_slice(slice);
1580
+ }
1581
+
1582
+ return result;
1583
+ }
1584
+
1585
+ //////////////////////
1586
+ // Helper Functions //
1587
+ //////////////////////
1588
+
1589
+ static VALUE elementwise_op(nm::ewop_t op, VALUE left_val, VALUE right_val) {
1590
+
1591
+ NMATRIX* left;
1592
+ NMATRIX* result;
1593
+
1594
+ CheckNMatrixType(left_val);
1595
+ UnwrapNMatrix(left_val, left);
1596
+
1597
+ if (TYPE(right_val) != T_DATA || (RDATA(right_val)->dfree != (RUBY_DATA_FUNC)nm_delete && RDATA(right_val)->dfree != (RUBY_DATA_FUNC)nm_delete_ref)) {
1598
+ // This is a matrix-scalar element-wise operation.
1599
+ std::string sym;
1600
+ switch(left->stype) {
1601
+ case nm::DENSE_STORE:
1602
+ sym = "__dense_scalar_" + nm::EWOP_NAMES[op] + "__";
1603
+ break;
1604
+ case nm::YALE_STORE:
1605
+ sym = "__yale_scalar_" + nm::EWOP_NAMES[op] + "__";
1606
+ break;
1607
+ case nm::LIST_STORE:
1608
+ sym = "__list_scalar_" + nm::EWOP_NAMES[op] + "__";
1609
+ break;
1610
+ default:
1611
+ rb_raise(rb_eNotImpError, "unknown storage type requested scalar element-wise operation");
1612
+ }
1613
+ return rb_funcall(left_val, rb_intern(sym.c_str()), 1, right_val);
1614
+
1615
+ } else {
1616
+
1617
+ // Check that the left- and right-hand sides have the same dimensionality.
1618
+ if (NM_DIM(left_val) != NM_DIM(right_val)) {
1619
+ rb_raise(rb_eArgError, "The left- and right-hand sides of the operation must have the same dimensionality.");
1620
+ }
1621
+
1622
+ // Check that the left- and right-hand sides have the same shape.
1623
+ if (memcmp(&NM_SHAPE(left_val, 0), &NM_SHAPE(right_val, 0), sizeof(size_t) * NM_DIM(left_val)) != 0) {
1624
+ rb_raise(rb_eArgError, "The left- and right-hand sides of the operation must have the same shape.");
1625
+ }
1626
+
1627
+ NMATRIX* right;
1628
+ UnwrapNMatrix(right_val, right);
1629
+
1630
+ if (left->stype == right->stype) {
1631
+ std::string sym;
1632
+
1633
+ switch(left->stype) {
1634
+ case nm::DENSE_STORE:
1635
+ sym = "__dense_elementwise_" + nm::EWOP_NAMES[op] + "__";
1636
+ break;
1637
+ case nm::YALE_STORE:
1638
+ sym = "__yale_elementwise_" + nm::EWOP_NAMES[op] + "__";
1639
+ break;
1640
+ case nm::LIST_STORE:
1641
+ sym = "__list_elementwise_" + nm::EWOP_NAMES[op] + "__";
1642
+ break;
1643
+ default:
1644
+ rb_raise(rb_eNotImpError, "unknown storage type requested element-wise operation");
1645
+ }
1646
+ return rb_funcall(left_val, rb_intern(sym.c_str()), 1, right_val);
1647
+
1648
+ } else {
1649
+ rb_raise(rb_eArgError, "Element-wise operations are not currently supported between matrices with differing stypes.");
1650
+ }
1651
+ }
1652
+
1653
+ return Data_Wrap_Struct(CLASS_OF(left_val), nm_mark, nm_delete, result);
1654
+ }
1655
+
1656
+ /*
1657
+ * Check to determine whether matrix is a reference to another matrix.
1658
+ */
1659
+ bool is_ref(const NMATRIX* matrix) {
1660
+ return matrix->storage->src != matrix->storage;
1661
+ }
1662
+
1663
+ /*
1664
+ * Helper function for nm_symmetric and nm_hermitian.
1665
+ */
1666
+ static VALUE is_symmetric(VALUE self, bool hermitian) {
1667
+ NMATRIX* m;
1668
+ UnwrapNMatrix(self, m);
1669
+
1670
+ if (m->storage->shape[0] == m->storage->shape[1] and m->storage->dim == 2) {
1671
+ if (NM_STYPE(self) == nm::DENSE_STORE) {
1672
+ if (hermitian) {
1673
+ nm_dense_storage_is_hermitian((DENSE_STORAGE*)(m->storage), m->storage->shape[0]);
1674
+
1675
+ } else {
1676
+ nm_dense_storage_is_symmetric((DENSE_STORAGE*)(m->storage), m->storage->shape[0]);
1677
+ }
1678
+
1679
+ } else {
1680
+ // TODO: Implement, at the very least, yale_is_symmetric. Model it after yale/transp.template.c.
1681
+ rb_raise(rb_eNotImpError, "symmetric? and hermitian? only implemented for dense currently");
1682
+ }
1683
+
1684
+ }
1685
+
1686
+ return Qfalse;
1687
+ }
1688
+
1689
+ ///////////////////////
1690
+ // Utility Functions //
1691
+ ///////////////////////
1692
+
1693
+ /*
1694
+ * Guess the dtype given a Ruby VALUE and return it as a symbol.
1695
+ *
1696
+ * Not to be confused with nm_dtype_guess, which returns an nm::dtype_t. (This calls that.)
1697
+ */
1698
+ static VALUE nm_guess_dtype(VALUE self, VALUE v) {
1699
+ return ID2SYM(rb_intern(DTYPE_NAMES[nm_dtype_guess(v)]));
1700
+ }
1701
+
1702
+ /*
1703
+ * Get the minimum allowable dtype for a Ruby VALUE and return it as a symbol.
1704
+ */
1705
+ static VALUE nm_min_dtype(VALUE self, VALUE v) {
1706
+ return ID2SYM(rb_intern(DTYPE_NAMES[nm_dtype_min(v)]));
1707
+ }
1708
+
1709
+ /*
1710
+ * Helper for nm_dtype_min(), handling integers.
1711
+ */
1712
+ nm::dtype_t nm_dtype_min_fixnum(int64_t v) {
1713
+ if (v >= 0 && v <= UCHAR_MAX) return nm::BYTE;
1714
+ else {
1715
+ v = std::abs(v);
1716
+ if (v <= CHAR_MAX) return nm::INT8;
1717
+ else if (v <= SHRT_MAX) return nm::INT16;
1718
+ else if (v <= INT_MAX) return nm::INT32;
1719
+ else return nm::INT64;
1720
+ }
1721
+ }
1722
+
1723
+ /*
1724
+ * Helper for nm_dtype_min(), handling rationals.
1725
+ */
1726
+ nm::dtype_t nm_dtype_min_rational(VALUE vv) {
1727
+ nm::Rational128* v = ALLOCA_N(nm::Rational128, 1);
1728
+ rubyval_to_cval(vv, nm::RATIONAL128, v);
1729
+
1730
+ int64_t i = std::max(std::abs(v->n), v->d);
1731
+ if (i <= SHRT_MAX) return nm::INT16;
1732
+ else if (i <= INT_MAX) return nm::INT32;
1733
+ else return nm::INT64;
1734
+ }
1735
+
1736
+ /*
1737
+ * Return the minimum dtype required to store a given value.
1738
+ *
1739
+ * This is kind of arbitrary. For Float, it always returns :float32 for example, since in some cases neither :float64
1740
+ * not :float32 are sufficient.
1741
+ *
1742
+ * This function is used in upcasting for scalar math. We want to ensure that :int8 + 1 does not return an :int64, basically.
1743
+ *
1744
+ * FIXME: Eventually, this function should actually look at the value stored in Fixnums (for example), so that it knows
1745
+ * whether to return :int64 or :int32.
1746
+ */
1747
+ nm::dtype_t nm_dtype_min(VALUE v) {
1748
+
1749
+ switch(TYPE(v)) {
1750
+ case T_FIXNUM:
1751
+ return nm_dtype_min_fixnum(FIX2LONG(v));
1752
+ case T_BIGNUM:
1753
+ return nm::INT64;
1754
+ case T_FLOAT:
1755
+ return nm::FLOAT32;
1756
+ case T_COMPLEX:
1757
+ return nm::COMPLEX64;
1758
+ case T_RATIONAL:
1759
+ return nm_dtype_min_rational(v);
1760
+ case T_STRING:
1761
+ return RSTRING_LEN(v) == 1 ? nm::BYTE : nm::RUBYOBJ;
1762
+ case T_TRUE:
1763
+ case T_FALSE:
1764
+ case T_NIL:
1765
+ default:
1766
+ return nm::RUBYOBJ;
1767
+ }
1768
+ }
1769
+
1770
+
1771
+ /*
1772
+ * Guess the data type given a value.
1773
+ *
1774
+ * TODO: Probably needs some work for Bignum.
1775
+ */
1776
+ nm::dtype_t nm_dtype_guess(VALUE v) {
1777
+ switch(TYPE(v)) {
1778
+ case T_TRUE:
1779
+ case T_FALSE:
1780
+ case T_NIL:
1781
+ return nm::RUBYOBJ;
1782
+ case T_STRING:
1783
+ return RSTRING_LEN(v) == 1 ? nm::BYTE : nm::RUBYOBJ;
1784
+
1785
+ #if SIZEOF_INT == 8
1786
+ case T_FIXNUM:
1787
+ return nm::INT64;
1788
+
1789
+ case T_RATIONAL:
1790
+ return nm::RATIONAL128;
1791
+
1792
+ #else
1793
+ # if SIZEOF_INT == 4
1794
+ case T_FIXNUM:
1795
+ return nm::INT32;
1796
+
1797
+ case T_RATIONAL:
1798
+ return nm::RATIONAL64;
1799
+
1800
+ #else
1801
+ case T_FIXNUM:
1802
+ return nm::INT16;
1803
+
1804
+ case T_RATIONAL:
1805
+ return nm::RATIONAL32;
1806
+ # endif
1807
+ #endif
1808
+
1809
+ case T_BIGNUM:
1810
+ return nm::INT64;
1811
+
1812
+ #if SIZEOF_FLOAT == 4
1813
+ case T_COMPLEX:
1814
+ return nm::COMPLEX128;
1815
+
1816
+ case T_FLOAT:
1817
+ return nm::FLOAT64;
1818
+
1819
+ #else
1820
+ # if SIZEOF_FLOAT == 2
1821
+ case T_COMPLEX:
1822
+ return nm::COMPLEX64;
1823
+
1824
+ case T_FLOAT:
1825
+ return nm::FLOAT32;
1826
+ # endif
1827
+ #endif
1828
+
1829
+ case T_ARRAY:
1830
+ /*
1831
+ * May be passed for dense -- for now, just look at the first element.
1832
+ *
1833
+ * TODO: Look at entire array for most specific type.
1834
+ */
1835
+
1836
+ return nm_dtype_guess(RARRAY_PTR(v)[0]);
1837
+
1838
+ default:
1839
+ RB_P(v);
1840
+ rb_raise(rb_eArgError, "Unable to guess a data type from provided parameters; data type must be specified manually.");
1841
+ }
1842
+ }
1843
+
1844
+
1845
+
1846
+ /*
1847
+ * Allocate and return a SLICE object, which will contain the appropriate coordinate and length information for
1848
+ * accessing some part of a matrix.
1849
+ */
1850
+ static SLICE* get_slice(size_t dim, int argc, VALUE* arg, size_t* shape) {
1851
+ VALUE beg, end;
1852
+ int excl;
1853
+
1854
+ SLICE* slice = alloc_slice(dim);
1855
+ slice->single = true;
1856
+
1857
+ // r is the shape position; t is the slice position. They may differ when we're dealing with a
1858
+ // matrix where the effective dimension is less than the dimension (e.g., a vector).
1859
+ for (size_t r = 0, t = 0; r < dim; ++r) {
1860
+ VALUE v = t == argc ? Qnil : arg[t];
1861
+
1862
+ // if the current shape indicates a vector and fewer args were supplied than necessary, just use 0
1863
+ if (argc - t + r < dim && shape[r] == 1) {
1864
+ slice->coords[r] = 0;
1865
+ slice->lengths[r] = 1;
1866
+
1867
+ } else if (FIXNUM_P(v)) { // this used CLASS_OF before, which is inefficient for fixnum
1868
+
1869
+ slice->coords[r] = FIX2UINT(v);
1870
+ slice->lengths[r] = 1;
1871
+ t++;
1872
+
1873
+ } else if (SYMBOL_P(v) && rb_to_id(v) == nm_rb_mul) { // :* means the whole possible range
1874
+
1875
+ slice->coords[r] = 0;
1876
+ slice->lengths[r] = shape[r];
1877
+ slice->single = false;
1878
+
1879
+ } else if (TYPE(arg[t]) == T_HASH) { // 3:5 notation (inclusive)
1880
+ VALUE begin_end = rb_funcall(v, rb_intern("shift"), 0); // rb_hash_shift
1881
+ slice->coords[r] = FIX2UINT(rb_ary_entry(begin_end, 0));
1882
+ slice->lengths[r] = FIX2UINT(rb_ary_entry(begin_end, 1)) - slice->coords[r];
1883
+
1884
+ if (RHASH_EMPTY_P(v)) t++; // go on to the next
1885
+
1886
+ slice->single = false;
1887
+
1888
+ } else if (CLASS_OF(v) == rb_cRange) {
1889
+ rb_range_values(arg[t], &beg, &end, &excl);
1890
+ slice->coords[r] = FIX2UINT(beg);
1891
+ // Exclude last element for a...b range
1892
+ slice->lengths[r] = FIX2UINT(end) - slice->coords[r] + (excl ? 0 : 1);
1893
+
1894
+ slice->single = false;
1895
+
1896
+ t++;
1897
+
1898
+ } else {
1899
+ rb_raise(rb_eArgError, "expected Fixnum, Range, or Hash for slice component instead of %s", rb_obj_classname(v));
1900
+ }
1901
+
1902
+ if (slice->coords[r] > shape[r] || slice->coords[r] + slice->lengths[r] > shape[r])
1903
+ rb_raise(rb_eRangeError, "slice is larger than matrix in dimension %u (slice component %u)", r, t);
1904
+ }
1905
+
1906
+ return slice;
1907
+ }
1908
+
1909
+ #ifdef BENCHMARK
1910
+ /*
1911
+ * A simple function used when benchmarking NMatrix.
1912
+ */
1913
+ static double get_time(void) {
1914
+ struct timeval t;
1915
+ struct timezone tzp;
1916
+
1917
+ gettimeofday(&t, &tzp);
1918
+
1919
+ return t.tv_sec + t.tv_usec*1e-6;
1920
+ }
1921
+ #endif
1922
+
1923
+ /*
1924
+ * The argv parameter will be either 1 or 2 elements. If 1, could be either
1925
+ * initial or dtype. If 2, is initial and dtype. This function returns the
1926
+ * dtype.
1927
+ */
1928
+ static nm::dtype_t interpret_dtype(int argc, VALUE* argv, nm::stype_t stype) {
1929
+ int offset;
1930
+
1931
+ switch (argc) {
1932
+ case 1:
1933
+ offset = 0;
1934
+ break;
1935
+
1936
+ case 2:
1937
+ offset = 1;
1938
+ break;
1939
+
1940
+ default:
1941
+ rb_raise(rb_eArgError, "Need an initial value or a dtype.");
1942
+ break;
1943
+ }
1944
+
1945
+ if (SYMBOL_P(argv[offset])) {
1946
+ return nm_dtype_from_rbsymbol(argv[offset]);
1947
+
1948
+ } else if (TYPE(argv[offset]) == T_STRING) {
1949
+ return nm_dtype_from_rbstring(StringValue(argv[offset]));
1950
+
1951
+ } else if (stype == nm::YALE_STORE) {
1952
+ rb_raise(rb_eArgError, "Yale storage class requires a dtype.");
1953
+
1954
+ } else {
1955
+ return nm_dtype_guess(argv[0]);
1956
+ }
1957
+ }
1958
+
1959
+ /*
1960
+ * Convert an Ruby value or an array of Ruby values into initial C values.
1961
+ */
1962
+ static void* interpret_initial_value(VALUE arg, nm::dtype_t dtype) {
1963
+ unsigned int index;
1964
+ void* init_val;
1965
+
1966
+ if (TYPE(arg) == T_ARRAY) {
1967
+ // Array
1968
+ init_val = ALLOC_N(char, DTYPE_SIZES[dtype] * RARRAY_LEN(arg));
1969
+ NM_CHECK_ALLOC(init_val);
1970
+ for (index = 0; index < RARRAY_LEN(arg); ++index) {
1971
+ rubyval_to_cval(RARRAY_PTR(arg)[index], dtype, (char*)init_val + (index * DTYPE_SIZES[dtype]));
1972
+ }
1973
+
1974
+ } else {
1975
+ // Single value
1976
+ init_val = rubyobj_to_cval(arg, dtype);
1977
+ }
1978
+
1979
+ return init_val;
1980
+ }
1981
+
1982
+ /*
1983
+ * Convert the shape argument, which may be either a Ruby value or an array of
1984
+ * Ruby values, into C values. The second argument is where the dimensionality
1985
+ * of the matrix will be stored. The function itself returns a pointer to the
1986
+ * array describing the shape, which must be freed manually.
1987
+ */
1988
+ static size_t* interpret_shape(VALUE arg, size_t* dim) {
1989
+ size_t* shape;
1990
+
1991
+ if (TYPE(arg) == T_ARRAY) {
1992
+ *dim = RARRAY_LEN(arg);
1993
+ shape = ALLOC_N(size_t, *dim);
1994
+
1995
+ for (size_t index = 0; index < *dim; ++index) {
1996
+ shape[index] = FIX2UINT( RARRAY_PTR(arg)[index] );
1997
+ }
1998
+
1999
+ } else if (FIXNUM_P(arg)) {
2000
+ *dim = 2;
2001
+ shape = ALLOC_N(size_t, *dim);
2002
+
2003
+ shape[0] = FIX2UINT(arg);
2004
+ shape[1] = FIX2UINT(arg);
2005
+
2006
+ } else {
2007
+ rb_raise(rb_eArgError, "Expected an array of numbers or a single Fixnum for matrix shape");
2008
+ }
2009
+
2010
+ return shape;
2011
+ }
2012
+
2013
+ /*
2014
+ * Convert a Ruby symbol or string into an storage type.
2015
+ */
2016
+ static nm::stype_t interpret_stype(VALUE arg) {
2017
+ if (SYMBOL_P(arg)) {
2018
+ return nm_stype_from_rbsymbol(arg);
2019
+
2020
+ } else if (TYPE(arg) == T_STRING) {
2021
+ return nm_stype_from_rbstring(StringValue(arg));
2022
+
2023
+ } else {
2024
+ rb_raise(rb_eArgError, "Expected storage type");
2025
+ }
2026
+ }
2027
+
2028
+ //////////////////
2029
+ // Math Helpers //
2030
+ //////////////////
2031
+
2032
+ STORAGE* matrix_storage_cast_alloc(NMATRIX* matrix, nm::dtype_t new_dtype) {
2033
+ if (matrix->storage->dtype == new_dtype && !is_ref(matrix))
2034
+ return matrix->storage;
2035
+
2036
+ CAST_TABLE(cast_copy_storage);
2037
+ return cast_copy_storage[matrix->stype][matrix->stype](matrix->storage, new_dtype, NULL);
2038
+ }
2039
+
2040
+ STORAGE_PAIR binary_storage_cast_alloc(NMATRIX* left_matrix, NMATRIX* right_matrix) {
2041
+ STORAGE_PAIR casted;
2042
+ nm::dtype_t new_dtype = Upcast[left_matrix->storage->dtype][right_matrix->storage->dtype];
2043
+
2044
+ casted.left = matrix_storage_cast_alloc(left_matrix, new_dtype);
2045
+ casted.right = matrix_storage_cast_alloc(right_matrix, new_dtype);
2046
+
2047
+ return casted;
2048
+ }
2049
+
2050
+ static VALUE matrix_multiply_scalar(NMATRIX* left, VALUE scalar) {
2051
+ rb_raise(rb_eNotImpError, "matrix-scalar multiplication not implemented yet");
2052
+ return Qnil;
2053
+ }
2054
+
2055
+ static VALUE matrix_multiply(NMATRIX* left, NMATRIX* right) {
2056
+ ///TODO: multiplication for non-dense and/or non-decimal matrices
2057
+
2058
+ // Make sure both of our matrices are of the correct type.
2059
+ STORAGE_PAIR casted = binary_storage_cast_alloc(left, right);
2060
+
2061
+ size_t* resulting_shape = ALLOC_N(size_t, 2);
2062
+ resulting_shape[0] = left->storage->shape[0];
2063
+ resulting_shape[1] = right->storage->shape[1];
2064
+
2065
+ // Sometimes we only need to use matrix-vector multiplication (e.g., GEMM versus GEMV). Find out.
2066
+ bool vector = false;
2067
+ if (resulting_shape[1] == 1) vector = true;
2068
+
2069
+ static STORAGE* (*storage_matrix_multiply[nm::NUM_STYPES])(const STORAGE_PAIR&, size_t*, bool) = {
2070
+ nm_dense_storage_matrix_multiply,
2071
+ nm_list_storage_matrix_multiply,
2072
+ nm_yale_storage_matrix_multiply
2073
+ };
2074
+
2075
+ STORAGE* resulting_storage = storage_matrix_multiply[left->stype](casted, resulting_shape, vector);
2076
+ NMATRIX* result = nm_create(left->stype, resulting_storage);
2077
+
2078
+ // Free any casted-storage we created for the multiplication.
2079
+ // TODO: Can we make the Ruby GC take care of this stuff now that we're using it?
2080
+ // If we did that, we night not have to re-create these every time, right? Or wrong? Need to do
2081
+ // more research.
2082
+ static void (*free_storage[nm::NUM_STYPES])(STORAGE*) = {
2083
+ nm_dense_storage_delete,
2084
+ nm_list_storage_delete,
2085
+ nm_yale_storage_delete
2086
+ };
2087
+
2088
+ if (left->storage != casted.left) free_storage[result->stype](casted.left);
2089
+ if (right->storage != casted.right) free_storage[result->stype](casted.right);
2090
+
2091
+ if (result) return Data_Wrap_Struct(cNMatrix, nm_mark, nm_delete, result);
2092
+ return Qnil; // Only if we try to multiply list matrices should we return Qnil.
2093
+ }
2094
+
2095
+ /*
2096
+ * Calculate the exact determinant of a dense matrix.
2097
+ *
2098
+ * Returns nil for dense matrices which are not square or number of dimensions other than 2.
2099
+ *
2100
+ * Note: Currently only implemented for 2x2 and 3x3 matrices.
2101
+ */
2102
+ static VALUE nm_det_exact(VALUE self) {
2103
+ if (NM_STYPE(self) != nm::DENSE_STORE) rb_raise(nm_eStorageTypeError, "can only calculate exact determinant for dense matrices");
2104
+
2105
+ if (NM_DIM(self) != 2 || NM_SHAPE0(self) != NM_SHAPE1(self)) return Qnil;
2106
+
2107
+ // Calculate the determinant and then assign it to the return value
2108
+ void* result = ALLOCA_N(char, DTYPE_SIZES[NM_DTYPE(self)]);
2109
+ nm_math_det_exact(NM_SHAPE0(self), NM_STORAGE_DENSE(self)->elements, NM_SHAPE0(self), NM_DTYPE(self), result);
2110
+
2111
+ return rubyobj_from_cval(result, NM_DTYPE(self)).rval;
2112
+ }
2113
+
2114
+ /////////////////
2115
+ // Exposed API //
2116
+ /////////////////
2117
+
2118
+ /*
2119
+ * Create a dense matrix. Used by the NMatrix GSL fork. Unlike nm_create, this one copies all of the
2120
+ * arrays and such passed in -- so you don't have to allocate and pass a new shape object for every
2121
+ * matrix you want to create, for example. Same goes for elements.
2122
+ *
2123
+ * Returns a properly-wrapped Ruby object as a VALUE.
2124
+ *
2125
+ * *** Note that this function is for API only. Please do not use it internally.
2126
+ *
2127
+ * TODO: Add a column-major option for libraries that use column-major matrices.
2128
+ */
2129
+ VALUE rb_nmatrix_dense_create(nm::dtype_t dtype, size_t* shape, size_t dim, void* elements, size_t length) {
2130
+ NMATRIX* nm;
2131
+ size_t nm_dim;
2132
+ size_t* shape_copy;
2133
+
2134
+ // Do not allow a dim of 1. Treat it as a column or row matrix.
2135
+ if (dim == 1) {
2136
+ nm_dim = 2;
2137
+ shape_copy = ALLOC_N(size_t, nm_dim);
2138
+ shape_copy[0] = shape[0];
2139
+ shape_copy[1] = 1;
2140
+
2141
+ } else {
2142
+ nm_dim = dim;
2143
+ shape_copy = ALLOC_N(size_t, nm_dim);
2144
+ memcpy(shape_copy, shape, sizeof(size_t)*nm_dim);
2145
+ }
2146
+
2147
+ // Copy elements
2148
+ void* elements_copy = ALLOC_N(char, DTYPE_SIZES[dtype]*length);
2149
+ memcpy(elements_copy, elements, DTYPE_SIZES[dtype]*length);
2150
+
2151
+ // allocate and create the matrix and its storage
2152
+ nm = nm_create(nm::DENSE_STORE, nm_dense_storage_create(dtype, shape_copy, dim, elements_copy, length));
2153
+
2154
+ // tell Ruby about the matrix and its storage, particularly how to garbage collect it.
2155
+ return Data_Wrap_Struct(cNMatrix, nm_mark, nm_delete, nm);
2156
+ }
2157
+
2158
+ /*
2159
+ * Create a dense vector. Used by the NMatrix GSL fork.
2160
+ *
2161
+ * Basically just a convenience wrapper for rb_nmatrix_dense_create().
2162
+ *
2163
+ * Returns a properly-wrapped Ruby NMatrix object as a VALUE. Included for backwards compatibility
2164
+ * for when NMatrix had an NVector class.
2165
+ */
2166
+ VALUE rb_nvector_dense_create(nm::dtype_t dtype, void* elements, size_t length) {
2167
+ size_t dim = 1, shape = length;
2168
+ return rb_nmatrix_dense_create(dtype, &shape, dim, elements, length);
2169
+ }