nmatrix 0.0.8 → 0.0.9

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