nmatrix 0.0.8 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -8
  3. data/.rspec +1 -1
  4. data/.travis.yml +12 -0
  5. data/CONTRIBUTING.md +27 -12
  6. data/Gemfile +1 -0
  7. data/History.txt +38 -0
  8. data/Manifest.txt +15 -15
  9. data/README.rdoc +7 -6
  10. data/Rakefile +40 -5
  11. data/ext/nmatrix/data/data.cpp +2 -37
  12. data/ext/nmatrix/data/data.h +19 -121
  13. data/ext/nmatrix/data/meta.h +70 -0
  14. data/ext/nmatrix/extconf.rb +40 -12
  15. data/ext/nmatrix/math/math.h +13 -103
  16. data/ext/nmatrix/nmatrix.cpp +10 -2018
  17. data/ext/nmatrix/nmatrix.h +16 -13
  18. data/ext/nmatrix/ruby_constants.cpp +12 -1
  19. data/ext/nmatrix/ruby_constants.h +7 -1
  20. data/ext/nmatrix/ruby_nmatrix.c +2169 -0
  21. data/ext/nmatrix/storage/dense.cpp +123 -14
  22. data/ext/nmatrix/storage/dense.h +10 -4
  23. data/ext/nmatrix/storage/list.cpp +265 -48
  24. data/ext/nmatrix/storage/list.h +6 -9
  25. data/ext/nmatrix/storage/storage.cpp +44 -54
  26. data/ext/nmatrix/storage/storage.h +2 -2
  27. data/ext/nmatrix/storage/yale/class.h +1070 -0
  28. data/ext/nmatrix/storage/yale/iterators/base.h +142 -0
  29. data/ext/nmatrix/storage/yale/iterators/iterator.h +130 -0
  30. data/ext/nmatrix/storage/yale/iterators/row.h +449 -0
  31. data/ext/nmatrix/storage/yale/iterators/row_stored.h +139 -0
  32. data/ext/nmatrix/storage/yale/iterators/row_stored_nd.h +167 -0
  33. data/ext/nmatrix/storage/yale/iterators/stored_diagonal.h +123 -0
  34. data/ext/nmatrix/storage/yale/math/transpose.h +110 -0
  35. data/ext/nmatrix/storage/yale/yale.cpp +1785 -0
  36. data/ext/nmatrix/storage/{yale.h → yale/yale.h} +23 -55
  37. data/ext/nmatrix/types.h +2 -0
  38. data/ext/nmatrix/util/io.cpp +27 -45
  39. data/ext/nmatrix/util/io.h +0 -2
  40. data/ext/nmatrix/util/sl_list.cpp +169 -28
  41. data/ext/nmatrix/util/sl_list.h +9 -3
  42. data/lib/nmatrix/blas.rb +20 -20
  43. data/lib/nmatrix/enumerate.rb +1 -1
  44. data/lib/nmatrix/io/mat5_reader.rb +8 -14
  45. data/lib/nmatrix/lapack.rb +3 -3
  46. data/lib/nmatrix/math.rb +3 -3
  47. data/lib/nmatrix/nmatrix.rb +19 -5
  48. data/lib/nmatrix/nvector.rb +2 -0
  49. data/lib/nmatrix/shortcuts.rb +90 -125
  50. data/lib/nmatrix/version.rb +1 -1
  51. data/nmatrix.gemspec +7 -8
  52. data/spec/{nmatrix_spec.rb → 00_nmatrix_spec.rb} +45 -208
  53. data/spec/01_enum_spec.rb +184 -0
  54. data/spec/{slice_spec.rb → 02_slice_spec.rb} +55 -39
  55. data/spec/blas_spec.rb +22 -54
  56. data/spec/elementwise_spec.rb +9 -8
  57. data/spec/io_spec.rb +6 -4
  58. data/spec/lapack_spec.rb +26 -26
  59. data/spec/math_spec.rb +9 -5
  60. data/spec/nmatrix_yale_spec.rb +29 -61
  61. data/spec/shortcuts_spec.rb +34 -22
  62. data/spec/slice_set_spec.rb +157 -0
  63. data/spec/spec_helper.rb +42 -2
  64. data/spec/stat_spec.rb +192 -0
  65. metadata +52 -55
  66. data/ext/nmatrix/storage/yale.cpp +0 -2284
  67. data/spec/nmatrix_list_spec.rb +0 -113
  68. data/spec/nvector_spec.rb +0 -112
@@ -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"