nmatrix 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. data/.gitignore +27 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +3 -5
  4. data/Guardfile +6 -0
  5. data/History.txt +33 -0
  6. data/Manifest.txt +41 -38
  7. data/README.rdoc +88 -11
  8. data/Rakefile +35 -53
  9. data/ext/nmatrix/data/complex.h +372 -0
  10. data/ext/nmatrix/data/data.cpp +275 -0
  11. data/ext/nmatrix/data/data.h +707 -0
  12. data/ext/nmatrix/data/rational.h +421 -0
  13. data/ext/nmatrix/data/ruby_object.h +446 -0
  14. data/ext/nmatrix/extconf.rb +101 -51
  15. data/ext/nmatrix/new_extconf.rb +56 -0
  16. data/ext/nmatrix/nmatrix.cpp +1609 -0
  17. data/ext/nmatrix/nmatrix.h +265 -849
  18. data/ext/nmatrix/ruby_constants.cpp +134 -0
  19. data/ext/nmatrix/ruby_constants.h +103 -0
  20. data/ext/nmatrix/storage/common.cpp +70 -0
  21. data/ext/nmatrix/storage/common.h +170 -0
  22. data/ext/nmatrix/storage/dense.cpp +665 -0
  23. data/ext/nmatrix/storage/dense.h +116 -0
  24. data/ext/nmatrix/storage/list.cpp +1088 -0
  25. data/ext/nmatrix/storage/list.h +129 -0
  26. data/ext/nmatrix/storage/storage.cpp +658 -0
  27. data/ext/nmatrix/storage/storage.h +99 -0
  28. data/ext/nmatrix/storage/yale.cpp +1601 -0
  29. data/ext/nmatrix/storage/yale.h +208 -0
  30. data/ext/nmatrix/ttable_helper.rb +126 -0
  31. data/ext/nmatrix/{yale/smmp1_header.template.c → types.h} +36 -9
  32. data/ext/nmatrix/util/io.cpp +295 -0
  33. data/ext/nmatrix/util/io.h +117 -0
  34. data/ext/nmatrix/util/lapack.h +1175 -0
  35. data/ext/nmatrix/util/math.cpp +557 -0
  36. data/ext/nmatrix/util/math.h +1363 -0
  37. data/ext/nmatrix/util/sl_list.cpp +475 -0
  38. data/ext/nmatrix/util/sl_list.h +255 -0
  39. data/ext/nmatrix/util/util.h +78 -0
  40. data/lib/nmatrix/blas.rb +70 -0
  41. data/lib/nmatrix/io/mat5_reader.rb +567 -0
  42. data/lib/nmatrix/io/mat_reader.rb +162 -0
  43. data/lib/{string.rb → nmatrix/monkeys.rb} +49 -2
  44. data/lib/nmatrix/nmatrix.rb +199 -0
  45. data/lib/nmatrix/nvector.rb +103 -0
  46. data/lib/nmatrix/version.rb +27 -0
  47. data/lib/nmatrix.rb +22 -230
  48. data/nmatrix.gemspec +59 -0
  49. data/scripts/mac-brew-gcc.sh +47 -0
  50. data/spec/4x4_sparse.mat +0 -0
  51. data/spec/4x5_dense.mat +0 -0
  52. data/spec/blas_spec.rb +47 -0
  53. data/spec/elementwise_spec.rb +164 -0
  54. data/spec/io_spec.rb +60 -0
  55. data/spec/lapack_spec.rb +52 -0
  56. data/spec/math_spec.rb +96 -0
  57. data/spec/nmatrix_spec.rb +93 -89
  58. data/spec/nmatrix_yale_spec.rb +52 -36
  59. data/spec/nvector_spec.rb +1 -1
  60. data/spec/slice_spec.rb +257 -0
  61. data/spec/spec_helper.rb +51 -0
  62. data/spec/utm5940.mtx +83844 -0
  63. metadata +113 -71
  64. data/.autotest +0 -23
  65. data/.gemtest +0 -0
  66. data/ext/nmatrix/cblas.c +0 -150
  67. data/ext/nmatrix/dense/blas_header.template.c +0 -52
  68. data/ext/nmatrix/dense/elementwise.template.c +0 -107
  69. data/ext/nmatrix/dense/gemm.template.c +0 -159
  70. data/ext/nmatrix/dense/gemv.template.c +0 -130
  71. data/ext/nmatrix/dense/rationalmath.template.c +0 -68
  72. data/ext/nmatrix/dense.c +0 -307
  73. data/ext/nmatrix/depend +0 -18
  74. data/ext/nmatrix/generator/syntax_tree.rb +0 -481
  75. data/ext/nmatrix/generator.rb +0 -594
  76. data/ext/nmatrix/list.c +0 -774
  77. data/ext/nmatrix/nmatrix.c +0 -1977
  78. data/ext/nmatrix/rational.c +0 -98
  79. data/ext/nmatrix/yale/complexmath.template.c +0 -71
  80. data/ext/nmatrix/yale/elementwise.template.c +0 -46
  81. data/ext/nmatrix/yale/elementwise_op.template.c +0 -73
  82. data/ext/nmatrix/yale/numbmm.template.c +0 -94
  83. data/ext/nmatrix/yale/smmp1.template.c +0 -21
  84. data/ext/nmatrix/yale/smmp2.template.c +0 -43
  85. data/ext/nmatrix/yale/smmp2_header.template.c +0 -46
  86. data/ext/nmatrix/yale/sort_columns.template.c +0 -56
  87. data/ext/nmatrix/yale/symbmm.template.c +0 -54
  88. data/ext/nmatrix/yale/transp.template.c +0 -68
  89. data/ext/nmatrix/yale.c +0 -726
  90. data/lib/array.rb +0 -67
  91. data/spec/syntax_tree_spec.rb +0 -46
@@ -0,0 +1,1088 @@
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 - 2012, Ruby Science Foundation
13
+ // NMatrix is Copyright (c) 2012, 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
+ // == list.c
25
+ //
26
+ // List-of-lists n-dimensional matrix storage. Uses singly-linked
27
+ // lists.
28
+
29
+ /*
30
+ * Standard Includes
31
+ */
32
+
33
+ #include <ruby.h>
34
+ #include <algorithm> // std::min
35
+ #include <iostream>
36
+
37
+ /*
38
+ * Project Includes
39
+ */
40
+
41
+ #include "types.h"
42
+
43
+ #include "data/data.h"
44
+
45
+ #include "common.h"
46
+ #include "list.h"
47
+
48
+ #include "util/math.h"
49
+ #include "util/sl_list.h"
50
+
51
+ /*
52
+ * Macros
53
+ */
54
+
55
+ /*
56
+ * Global Variables
57
+ */
58
+
59
+ namespace nm { namespace list_storage {
60
+
61
+ /*
62
+ * Forward Declarations
63
+ */
64
+
65
+ template <typename LDType, typename RDType>
66
+ static LIST_STORAGE* cast_copy(const LIST_STORAGE* rhs, dtype_t new_dtype);
67
+
68
+ template <typename LDType, typename RDType>
69
+ static bool eqeq(const LIST_STORAGE* left, const LIST_STORAGE* right);
70
+
71
+ template <ewop_t op, typename LDType, typename RDType>
72
+ static void* ew_op(LIST* dest, const LIST* left, const void* l_default, const LIST* right, const void* r_default, const size_t* shape, size_t dim);
73
+
74
+ template <ewop_t op, typename LDType, typename RDType>
75
+ static void ew_op_prime(LIST* dest, LDType d_default, const LIST* left, LDType l_default, const LIST* right, RDType r_default, const size_t* shape, size_t last_level, size_t level);
76
+
77
+ template <ewop_t op, typename LDType, typename RDType>
78
+ static void ew_comp_prime(LIST* dest, uint8_t d_default, const LIST* left, LDType l_default, const LIST* right, RDType r_default, const size_t* shape, size_t last_level, size_t level);
79
+
80
+ } // end of namespace list_storage
81
+
82
+ extern "C" {
83
+
84
+ /*
85
+ * Functions
86
+ */
87
+
88
+
89
+ ////////////////
90
+ // Lifecycle //
91
+ ///////////////
92
+
93
+ /*
94
+ * Creates a list-of-lists(-of-lists-of-lists-etc) storage framework for a
95
+ * matrix.
96
+ *
97
+ * Note: The pointers you pass in for shape and init_val become property of our
98
+ * new storage. You don't need to free them, and you shouldn't re-use them.
99
+ */
100
+ LIST_STORAGE* nm_list_storage_create(dtype_t dtype, size_t* shape, size_t dim, void* init_val) {
101
+ LIST_STORAGE* s;
102
+
103
+ s = ALLOC( LIST_STORAGE );
104
+
105
+ s->dim = dim;
106
+ s->shape = shape;
107
+ s->dtype = dtype;
108
+
109
+ s->offset = ALLOC_N(size_t, s->dim);
110
+ memset(s->offset, 0, s->dim * sizeof(size_t));
111
+
112
+ s->rows = list::create();
113
+ s->default_val = init_val;
114
+ s->count = 1;
115
+ s->src = s;
116
+
117
+ return s;
118
+ }
119
+
120
+ /*
121
+ * Documentation goes here.
122
+ */
123
+ void nm_list_storage_delete(STORAGE* s) {
124
+ if (s) {
125
+ LIST_STORAGE* storage = (LIST_STORAGE*)s;
126
+ if (storage->count-- == 1) {
127
+ list::del( storage->rows, storage->dim - 1 );
128
+
129
+ free(storage->shape);
130
+ free(storage->offset);
131
+ free(storage->default_val);
132
+ free(s);
133
+ }
134
+ }
135
+ }
136
+
137
+ /*
138
+ * Documentation goes here.
139
+ */
140
+ void nm_list_storage_delete_ref(STORAGE* s) {
141
+ if (s) {
142
+ LIST_STORAGE* storage = (LIST_STORAGE*)s;
143
+
144
+ nm_list_storage_delete( reinterpret_cast<STORAGE*>(storage->src ) );
145
+ free(storage->shape);
146
+ free(storage->offset);
147
+ free(s);
148
+ }
149
+ }
150
+
151
+ /*
152
+ * Documentation goes here.
153
+ */
154
+ void nm_list_storage_mark(void* storage_base) {
155
+ LIST_STORAGE* storage = (LIST_STORAGE*)storage_base;
156
+
157
+ if (storage && storage->dtype == RUBYOBJ) {
158
+ rb_gc_mark(*((VALUE*)(storage->default_val)));
159
+ list::mark(storage->rows, storage->dim - 1);
160
+ }
161
+ }
162
+
163
+ ///////////////
164
+ // Accessors //
165
+ ///////////////
166
+
167
+ /*
168
+ * Documentation goes here.
169
+ */
170
+ NODE* list_storage_get_single_node(LIST_STORAGE* s, SLICE* slice)
171
+ {
172
+ size_t r;
173
+ LIST* l = s->rows;
174
+ NODE* n;
175
+
176
+ for (r = 0; r < s->dim; r++) {
177
+ n = list::find(l, s->offset[r] + slice->coords[r]);
178
+ if (n) l = reinterpret_cast<LIST*>(n->val);
179
+ else return NULL;
180
+ }
181
+
182
+ return n;
183
+ }
184
+
185
+
186
+ static LIST* slice_copy(const LIST_STORAGE *src, LIST *src_rows, size_t *coords, size_t *lengths, size_t n)
187
+ {
188
+ NODE *src_node;
189
+ LIST *dst_rows = NULL;
190
+ void *val = NULL;
191
+ int key;
192
+
193
+ dst_rows = list::create();
194
+ src_node = src_rows->first;
195
+
196
+ while (src_node) {
197
+ key = src_node->key - (src->offset[n] + coords[n]);
198
+
199
+ if (key >= 0 && (size_t)key < lengths[n]) {
200
+ if (src->dim - n > 1) {
201
+ val = slice_copy(src,
202
+ reinterpret_cast<LIST*>(src_node->val),
203
+ coords,
204
+ lengths,
205
+ n + 1);
206
+
207
+ if (val)
208
+ list::insert_with_copy(dst_rows, key, val, sizeof(LIST));
209
+
210
+ }
211
+ else {
212
+ list::insert_with_copy(dst_rows, key, src_node->val, DTYPE_SIZES[src->dtype]);
213
+ }
214
+ }
215
+
216
+ src_node = src_node->next;
217
+ }
218
+
219
+ return dst_rows;
220
+ }
221
+
222
+ /*
223
+ * Documentation goes here.
224
+ */
225
+ void* nm_list_storage_get(STORAGE* storage, SLICE* slice) {
226
+ LIST_STORAGE* s = (LIST_STORAGE*)storage;
227
+ LIST_STORAGE* ns = NULL;
228
+ NODE* n;
229
+
230
+ if (slice->single) {
231
+ n = list_storage_get_single_node(s, slice);
232
+ return (n ? n->val : s->default_val);
233
+ }
234
+ else {
235
+ void *init_val = ALLOC_N(char, DTYPE_SIZES[s->dtype]);
236
+ memcpy(init_val, s->default_val, DTYPE_SIZES[s->dtype]);
237
+
238
+ size_t *shape = ALLOC_N(size_t, s->dim);
239
+ memcpy(shape, slice->lengths, sizeof(size_t) * s->dim);
240
+
241
+ ns = nm_list_storage_create(s->dtype, shape, s->dim, init_val);
242
+
243
+ ns->rows = slice_copy(s, s->rows, slice->coords, slice->lengths, 0);
244
+ return ns;
245
+ }
246
+ }
247
+
248
+ /*
249
+ * Get the contents of some set of coordinates. Note: Does not make a copy!
250
+ * Don't free!
251
+ */
252
+ void* nm_list_storage_ref(STORAGE* storage, SLICE* slice) {
253
+ LIST_STORAGE* s = (LIST_STORAGE*)storage;
254
+ LIST_STORAGE* ns = NULL;
255
+ NODE* n;
256
+
257
+ //TODO: It needs a refactoring.
258
+ if (slice->single) {
259
+ n = list_storage_get_single_node(s, slice);
260
+ return (n ? n->val : s->default_val);
261
+ }
262
+ else {
263
+ ns = ALLOC( LIST_STORAGE );
264
+
265
+ ns->dim = s->dim;
266
+ ns->dtype = s->dtype;
267
+ ns->offset = ALLOC_N(size_t, ns->dim);
268
+ ns->shape = ALLOC_N(size_t, ns->dim);
269
+
270
+ for (size_t i = 0; i < ns->dim; ++i) {
271
+ ns->offset[i] = slice->coords[i] + s->offset[i];
272
+ ns->shape[i] = slice->lengths[i];
273
+ }
274
+
275
+ ns->rows = s->rows;
276
+ ns->default_val = s->default_val;
277
+
278
+ s->src->count++;
279
+ ns->src = s->src;
280
+
281
+ return ns;
282
+ }
283
+ }
284
+
285
+ /*
286
+ * Documentation goes here.
287
+ *
288
+ * TODO: Allow this function to accept an entire row and not just one value -- for slicing
289
+ */
290
+ void* nm_list_storage_insert(STORAGE* storage, SLICE* slice, void* val) {
291
+ LIST_STORAGE* s = (LIST_STORAGE*)storage;
292
+ // Pretend dims = 2
293
+ // Then coords is going to be size 2
294
+ // So we need to find out if some key already exists
295
+ size_t r;
296
+ NODE* n;
297
+ LIST* l = s->rows;
298
+
299
+ // drill down into the structure
300
+ for (r = s->dim; r > 1; --r) {
301
+ n = list::insert(l, false, s->offset[s->dim - r] + slice->coords[s->dim - r], list::create());
302
+ l = reinterpret_cast<LIST*>(n->val);
303
+ }
304
+
305
+ n = list::insert(l, true, s->offset[s->dim - r] + slice->coords[s->dim - r], val);
306
+ return n->val;
307
+ }
308
+
309
+ /*
310
+ * Remove an item from list storage.
311
+ */
312
+ void* nm_list_storage_remove(STORAGE* storage, SLICE* slice) {
313
+ LIST_STORAGE* s = (LIST_STORAGE*)storage;
314
+ void* rm = NULL;
315
+
316
+ // This returns a boolean, which will indicate whether s->rows is empty.
317
+ // We can safely ignore it, since we never want to delete s->rows until
318
+ // it's time to destroy the LIST_STORAGE object.
319
+ list::remove_recursive(s->rows, slice->coords, s->offset, 0, s->dim, rm);
320
+
321
+ return rm;
322
+ }
323
+
324
+ ///////////
325
+ // Tests //
326
+ ///////////
327
+
328
+ /*
329
+ * Comparison of contents for list storage.
330
+ */
331
+ bool nm_list_storage_eqeq(const STORAGE* left, const STORAGE* right) {
332
+ NAMED_LR_DTYPE_TEMPLATE_TABLE(ttable, nm::list_storage::eqeq, bool, const LIST_STORAGE* left, const LIST_STORAGE* right);
333
+
334
+ return ttable[left->dtype][right->dtype]((const LIST_STORAGE*)left, (const LIST_STORAGE*)right);
335
+ }
336
+
337
+ //////////
338
+ // Math //
339
+ //////////
340
+
341
+ /*
342
+ * Element-wise operations for list storage.
343
+ */
344
+ STORAGE* nm_list_storage_ew_op(nm::ewop_t op, const STORAGE* left, const STORAGE* right) {
345
+ rb_raise(rb_eNotImpError, "elementwise operations for list storage currently broken");
346
+
347
+ OP_LR_DTYPE_TEMPLATE_TABLE(nm::list_storage::ew_op, void*, LIST* dest, const LIST* left, const void* l_default, const LIST* right, const void* r_default, const size_t* shape, size_t dim);
348
+
349
+ // We may need to upcast our arguments to the same type.
350
+ dtype_t new_dtype = Upcast[left->dtype][right->dtype];
351
+
352
+ // Make sure we allocate a byte-storing matrix for comparison operations; otherwise, use the argument dtype (new_dtype)
353
+ dtype_t result_dtype = static_cast<uint8_t>(op) < NUM_NONCOMP_EWOPS ? new_dtype : BYTE;
354
+
355
+
356
+
357
+ const LIST_STORAGE* l = reinterpret_cast<const LIST_STORAGE*>(left),
358
+ * r = reinterpret_cast<const LIST_STORAGE*>(right);
359
+
360
+ LIST_STORAGE* new_l = NULL;
361
+
362
+ // Allocate a new shape array for the resulting matrix.
363
+ size_t* new_shape = (size_t*)calloc(l->dim, sizeof(size_t));
364
+ memcpy(new_shape, left->shape, sizeof(size_t) * l->dim);
365
+
366
+ // Create the result matrix.
367
+ LIST_STORAGE* result = nm_list_storage_create(result_dtype, new_shape, left->dim, NULL);
368
+
369
+ /*
370
+ * Call the templated elementwise multiplication function and set the default
371
+ * value for the resulting matrix.
372
+ */
373
+ if (new_dtype != left->dtype) {
374
+ // Upcast the left-hand side if necessary.
375
+ new_l = reinterpret_cast<LIST_STORAGE*>(nm_list_storage_cast_copy(l, new_dtype));
376
+
377
+ result->default_val =
378
+ ttable[op][new_l->dtype][right->dtype](result->rows, new_l->rows, new_l->default_val, r->rows, r->default_val, result->shape, result->dim);
379
+
380
+ // Delete the temporary left-hand side matrix.
381
+ nm_list_storage_delete(reinterpret_cast<STORAGE*>(new_l));
382
+
383
+ } else {
384
+ result->default_val =
385
+ ttable[op][left->dtype][right->dtype](result->rows, l->rows, l->default_val, r->rows, r->default_val, result->shape, result->dim);
386
+ }
387
+
388
+ return result;
389
+ }
390
+
391
+
392
+ /*
393
+ * List storage matrix multiplication.
394
+ */
395
+ STORAGE* nm_list_storage_matrix_multiply(const STORAGE_PAIR& casted_storage, size_t* resulting_shape, bool vector) {
396
+ free(resulting_shape);
397
+ rb_raise(rb_eNotImpError, "multiplication not implemented for list-of-list matrices");
398
+ return NULL;
399
+ //DTYPE_TEMPLATE_TABLE(dense_storage::matrix_multiply, NMATRIX*, STORAGE_PAIR, size_t*, bool);
400
+
401
+ //return ttable[reinterpret_cast<DENSE_STORAGE*>(casted_storage.left)->dtype](casted_storage, resulting_shape, vector);
402
+ }
403
+
404
+
405
+ /*
406
+ * List storage to Hash conversion. Uses Hashes with default values, so you can continue to pretend
407
+ * it's a sparse matrix.
408
+ */
409
+ VALUE nm_list_storage_to_hash(const LIST_STORAGE* s, const dtype_t dtype) {
410
+
411
+ // Get the default value for the list storage.
412
+ VALUE default_value = rubyobj_from_cval(s->default_val, dtype).rval;
413
+
414
+ // Recursively copy each dimension of the matrix into a nested hash.
415
+ return nm_list_copy_to_hash(s->rows, dtype, s->dim - 1, default_value);
416
+ }
417
+
418
+ /////////////
419
+ // Utility //
420
+ /////////////
421
+
422
+ /*
423
+ * Recursively count the non-zero elements in a list storage object.
424
+ */
425
+ size_t nm_list_storage_count_elements_r(const LIST* l, size_t recursions) {
426
+ size_t count = 0;
427
+ NODE* curr = l->first;
428
+
429
+ if (recursions) {
430
+ while (curr) {
431
+ count += nm_list_storage_count_elements_r(reinterpret_cast<const LIST*>(curr->val), recursions - 1);
432
+ curr = curr->next;
433
+ }
434
+
435
+ } else {
436
+ while (curr) {
437
+ ++count;
438
+ curr = curr->next;
439
+ }
440
+ }
441
+
442
+ return count;
443
+ }
444
+
445
+ /*
446
+ * Count non-diagonal non-zero elements.
447
+ */
448
+ size_t nm_list_storage_count_nd_elements(const LIST_STORAGE* s) {
449
+ NODE *i_curr, *j_curr;
450
+ size_t count = 0;
451
+
452
+ if (s->dim != 2) {
453
+ rb_raise(rb_eNotImpError, "non-diagonal element counting only defined for dim = 2");
454
+ }
455
+
456
+ for (i_curr = s->rows->first; i_curr; i_curr = i_curr->next) {
457
+ int i = i_curr->key - s->offset[0];
458
+ if (i < 0 || i >= (int)s->shape[0]) continue;
459
+
460
+ for (j_curr = ((LIST*)(i_curr->val))->first; j_curr; j_curr = j_curr->next) {
461
+ int j = j_curr->key - s->offset[1];
462
+ if (j < 0 || j >= (int)s->shape[1]) continue;
463
+
464
+ if (i != j) ++count;
465
+ }
466
+ }
467
+
468
+ return count;
469
+ }
470
+
471
+ /////////////////////////
472
+ // Copying and Casting //
473
+ /////////////////////////
474
+ //
475
+ /*
476
+ * List storage copy constructor C access.
477
+ */
478
+
479
+ LIST_STORAGE* nm_list_storage_copy(const LIST_STORAGE* rhs)
480
+ {
481
+ size_t *shape = ALLOC_N(size_t, rhs->dim);
482
+ memcpy(shape, rhs->shape, sizeof(size_t) * rhs->dim);
483
+
484
+ void *init_val = ALLOC_N(char, DTYPE_SIZES[rhs->dtype]);
485
+ memcpy(init_val, rhs->default_val, DTYPE_SIZES[rhs->dtype]);
486
+
487
+ LIST_STORAGE* lhs = nm_list_storage_create(rhs->dtype, shape, rhs->dim, init_val);
488
+
489
+ lhs->rows = slice_copy(rhs, rhs->rows, lhs->offset, lhs->shape, 0);
490
+
491
+ return lhs;
492
+ }
493
+
494
+ /*
495
+ * List storage copy constructor C access with casting.
496
+ */
497
+ STORAGE* nm_list_storage_cast_copy(const STORAGE* rhs, dtype_t new_dtype) {
498
+ NAMED_LR_DTYPE_TEMPLATE_TABLE(ttable, nm::list_storage::cast_copy, LIST_STORAGE*, const LIST_STORAGE* rhs, dtype_t new_dtype);
499
+
500
+ return (STORAGE*)ttable[new_dtype][rhs->dtype]((LIST_STORAGE*)rhs, new_dtype);
501
+ }
502
+
503
+
504
+ /*
505
+ * List storage copy constructor for transposing.
506
+ */
507
+ STORAGE* nm_list_storage_copy_transposed(const STORAGE* rhs_base) {
508
+ rb_raise(rb_eNotImpError, "list storage transpose not yet implemented");
509
+ return NULL;
510
+ }
511
+
512
+
513
+ } // end of extern "C" block
514
+
515
+
516
+ /////////////////////////
517
+ // Templated Functions //
518
+ /////////////////////////
519
+
520
+ namespace list_storage {
521
+
522
+ /*
523
+ * List storage copy constructor for changing dtypes.
524
+ */
525
+ template <typename LDType, typename RDType>
526
+ static LIST_STORAGE* cast_copy(const LIST_STORAGE* rhs, dtype_t new_dtype) {
527
+
528
+ // allocate and copy shape
529
+ size_t* shape = ALLOC_N(size_t, rhs->dim);
530
+ memcpy(shape, rhs->shape, rhs->dim * sizeof(size_t));
531
+
532
+ // copy default value
533
+ LDType* default_val = ALLOC_N(LDType, 1);
534
+ *default_val = *reinterpret_cast<RDType*>(rhs->default_val);
535
+
536
+ LIST_STORAGE* lhs = nm_list_storage_create(new_dtype, shape, rhs->dim, default_val);
537
+ lhs->rows = list::create();
538
+
539
+ // TODO: Needs optimization. When matrix is reference it is copped twice.
540
+ if (rhs->src == rhs)
541
+ list::cast_copy_contents<LDType, RDType>(lhs->rows, rhs->rows, rhs->dim - 1);
542
+ else {
543
+ LIST_STORAGE *tmp = nm_list_storage_copy(rhs);
544
+ list::cast_copy_contents<LDType, RDType>(lhs->rows, tmp->rows, rhs->dim - 1);
545
+ nm_list_storage_delete(tmp);
546
+ }
547
+
548
+ return lhs;
549
+ }
550
+
551
+
552
+ /*
553
+ * Do these two dense matrices of the same dtype have exactly the same
554
+ * contents?
555
+ */
556
+ template <typename LDType, typename RDType>
557
+ bool eqeq(const LIST_STORAGE* left, const LIST_STORAGE* right) {
558
+ bool result;
559
+
560
+ // in certain cases, we need to keep track of the number of elements checked.
561
+ size_t num_checked = 0,
562
+ max_elements = nm_storage_count_max_elements(left);
563
+ LIST_STORAGE *tmp1 = NULL, *tmp2 = NULL;
564
+
565
+ if (!left->rows->first) {
566
+ // Easy: both lists empty -- just compare default values
567
+ if (!right->rows->first) {
568
+ return *reinterpret_cast<LDType*>(left->default_val) == *reinterpret_cast<RDType*>(right->default_val);
569
+
570
+ } else if (!list::eqeq_value<RDType,LDType>(right->rows, reinterpret_cast<LDType*>(left->default_val), left->dim-1, num_checked)) {
571
+ // Left empty, right not empty. Do all values in right == left->default_val?
572
+ return false;
573
+
574
+ } else if (num_checked < max_elements) {
575
+ // If the matrix isn't full, we also need to compare default values.
576
+ return *reinterpret_cast<LDType*>(left->default_val) == *reinterpret_cast<RDType*>(right->default_val);
577
+ }
578
+
579
+ } else if (!right->rows->first) {
580
+ // fprintf(stderr, "!right->rows true\n");
581
+ // Right empty, left not empty. Do all values in left == right->default_val?
582
+ if (!list::eqeq_value<LDType,RDType>(left->rows, reinterpret_cast<RDType*>(right->default_val), left->dim-1, num_checked)) {
583
+ return false;
584
+
585
+ } else if (num_checked < max_elements) {
586
+ // If the matrix isn't full, we also need to compare default values.
587
+ return *reinterpret_cast<LDType*>(left->default_val) == *reinterpret_cast<RDType*>(right->default_val);
588
+ }
589
+
590
+ } else {
591
+ // fprintf(stderr, "both matrices have entries\n");
592
+ // Hardest case. Compare lists node by node. Let's make it simpler by requiring that both have the same default value
593
+
594
+ // left is reference
595
+ if (left->src != left && right->src == right) {
596
+ tmp1 = nm_list_storage_copy(left);
597
+ result = list::eqeq<LDType,RDType>(tmp1->rows, right->rows, reinterpret_cast<LDType*>(left->default_val), reinterpret_cast<RDType*>(right->default_val), left->dim-1, num_checked);
598
+ nm_list_storage_delete(tmp1);
599
+ }
600
+ // right is reference
601
+ if (left->src == left && right->src != right) {
602
+ tmp2 = nm_list_storage_copy(right);
603
+ result = list::eqeq<LDType,RDType>(left->rows, tmp2->rows, reinterpret_cast<LDType*>(left->default_val), reinterpret_cast<RDType*>(right->default_val), left->dim-1, num_checked);
604
+ nm_list_storage_delete(tmp2);
605
+ }
606
+ // both are references
607
+ if (left->src != left && right->src != right) {
608
+ tmp1 = nm_list_storage_copy(left);
609
+ tmp2 = nm_list_storage_copy(right);
610
+ result = list::eqeq<LDType,RDType>(tmp1->rows, tmp2->rows, reinterpret_cast<LDType*>(left->default_val), reinterpret_cast<RDType*>(right->default_val), left->dim-1, num_checked);
611
+ nm_list_storage_delete(tmp1);
612
+ nm_list_storage_delete(tmp2);
613
+ }
614
+ // both are normal matricies
615
+ if (left->src == left && right->src == right)
616
+ result = list::eqeq<LDType,RDType>(left->rows, right->rows, reinterpret_cast<LDType*>(left->default_val), reinterpret_cast<RDType*>(right->default_val), left->dim-1, num_checked);
617
+
618
+ if (!result){
619
+ return result;
620
+ } else if (num_checked < max_elements) {
621
+ return *reinterpret_cast<LDType*>(left->default_val) == *reinterpret_cast<RDType*>(right->default_val);
622
+ }
623
+ }
624
+
625
+ return true;
626
+ }
627
+
628
+ /*
629
+ * List storage element-wise operations (including comparisons).
630
+ */
631
+ template <ewop_t op, typename LDType, typename RDType>
632
+ static void* ew_op(LIST* dest, const LIST* left, const void* l_default, const LIST* right, const void* r_default, const size_t* shape, size_t dim) {
633
+
634
+ if (static_cast<uint8_t>(op) < NUM_NONCOMP_EWOPS) {
635
+
636
+ /*
637
+ * Allocate space for, and calculate, the default value for the destination
638
+ * matrix.
639
+ */
640
+ LDType* d_default_mem = ALLOC(LDType);
641
+ *d_default_mem = ew_op_switch<op, LDType, RDType>(*reinterpret_cast<const LDType*>(l_default), *reinterpret_cast<const RDType*>(r_default));
642
+
643
+ // Now that setup is done call the actual elementwise operation function.
644
+ ew_op_prime<op, LDType, RDType>(dest, *reinterpret_cast<const LDType*>(d_default_mem),
645
+ left, *reinterpret_cast<const LDType*>(l_default),
646
+ right, *reinterpret_cast<const RDType*>(r_default),
647
+ shape, dim - 1, 0);
648
+
649
+ // Return a pointer to the destination matrix's default value.
650
+ return d_default_mem;
651
+
652
+ } else { // Handle comparison operations in a similar manner.
653
+ /*
654
+ * Allocate a byte for default, and set default value to 0.
655
+ */
656
+ uint8_t* d_default_mem = ALLOC(uint8_t);
657
+ *d_default_mem = 0;
658
+ switch (op) {
659
+ case EW_EQEQ:
660
+ *d_default_mem = *reinterpret_cast<const LDType*>(l_default) == *reinterpret_cast<const RDType*>(r_default);
661
+ break;
662
+
663
+ case EW_NEQ:
664
+ *d_default_mem = *reinterpret_cast<const LDType*>(l_default) != *reinterpret_cast<const RDType*>(r_default);
665
+ break;
666
+
667
+ case EW_LT:
668
+ *d_default_mem = *reinterpret_cast<const LDType*>(l_default) < *reinterpret_cast<const RDType*>(r_default);
669
+ break;
670
+
671
+ case EW_GT:
672
+ *d_default_mem = *reinterpret_cast<const LDType*>(l_default) > *reinterpret_cast<const RDType*>(r_default);
673
+ break;
674
+
675
+ case EW_LEQ:
676
+ *d_default_mem = *reinterpret_cast<const LDType*>(l_default) <= *reinterpret_cast<const RDType*>(r_default);
677
+ break;
678
+
679
+ case EW_GEQ:
680
+ *d_default_mem = *reinterpret_cast<const LDType*>(l_default) >= *reinterpret_cast<const RDType*>(r_default);
681
+ break;
682
+
683
+ default:
684
+ rb_raise(rb_eStandardError, "this should not happen");
685
+ }
686
+
687
+ // Now that setup is done call the actual elementwise comparison function.
688
+ ew_comp_prime<op, LDType, RDType>(dest, *reinterpret_cast<const uint8_t*>(d_default_mem),
689
+ left, *reinterpret_cast<const LDType*>(l_default),
690
+ right, *reinterpret_cast<const RDType*>(r_default),
691
+ shape, dim - 1, 0);
692
+
693
+ // Return a pointer to the destination matrix's default value.
694
+ return d_default_mem;
695
+ }
696
+ }
697
+
698
+
699
+ /*
700
+ * List storage element-wise comparisons, recursive helper.
701
+ */
702
+ template <ewop_t op, typename LDType, typename RDType>
703
+ static void ew_comp_prime(LIST* dest, uint8_t d_default, const LIST* left, LDType l_default, const LIST* right, RDType r_default, const size_t* shape, size_t last_level, size_t level) {
704
+
705
+ static LIST EMPTY_LIST = {NULL};
706
+
707
+ size_t index;
708
+
709
+ uint8_t tmp_result;
710
+
711
+ LIST* new_level = NULL;
712
+
713
+ NODE* l_node = left->first,
714
+ * r_node = right->first,
715
+ * dest_node = NULL;
716
+
717
+ for (index = 0; index < shape[level]; ++index) {
718
+ if (l_node == NULL and r_node == NULL) {
719
+ /*
720
+ * Both source lists are now empty. Because the default value of the
721
+ * destination is already set appropriately we can now return.
722
+ */
723
+
724
+ return;
725
+
726
+ } else {
727
+ // At least one list still has entries.
728
+
729
+ if (l_node == NULL and r_node->key == index) {
730
+ /*
731
+ * One source list is empty, but the index has caught up to the key of
732
+ * the other list.
733
+ */
734
+
735
+ if (level == last_level) {
736
+ switch (op) {
737
+ case EW_EQEQ:
738
+ tmp_result = (uint8_t)(l_default == *reinterpret_cast<RDType*>(r_node->val));
739
+ break;
740
+
741
+ case EW_NEQ:
742
+ tmp_result = (uint8_t)(l_default != *reinterpret_cast<RDType*>(r_node->val));
743
+ break;
744
+
745
+ case EW_LT:
746
+ tmp_result = (uint8_t)(l_default < *reinterpret_cast<RDType*>(r_node->val));
747
+ break;
748
+
749
+ case EW_GT:
750
+ tmp_result = (uint8_t)(l_default > *reinterpret_cast<RDType*>(r_node->val));
751
+ break;
752
+
753
+ case EW_LEQ:
754
+ tmp_result = (uint8_t)(l_default <= *reinterpret_cast<RDType*>(r_node->val));
755
+ break;
756
+
757
+ case EW_GEQ:
758
+ tmp_result = (uint8_t)(l_default >= *reinterpret_cast<RDType*>(r_node->val));
759
+ break;
760
+
761
+ default:
762
+ rb_raise(rb_eStandardError, "This should not happen.");
763
+ }
764
+
765
+ if (tmp_result != d_default) {
766
+ dest_node = nm::list::insert_helper(dest, dest_node, index, tmp_result);
767
+ }
768
+
769
+ } else {
770
+ new_level = nm::list::create();
771
+ dest_node = nm::list::insert_helper(dest, dest_node, index, new_level);
772
+
773
+ ew_comp_prime<op, LDType, RDType>(new_level, d_default, &EMPTY_LIST, l_default,
774
+ reinterpret_cast<LIST*>(r_node->val), r_default,
775
+ shape, last_level, level + 1);
776
+ }
777
+
778
+ r_node = r_node->next;
779
+
780
+ } else if (r_node == NULL and l_node->key == index) {
781
+ /*
782
+ * One source list is empty, but the index has caught up to the key of
783
+ * the other list.
784
+ */
785
+
786
+ if (level == last_level) {
787
+ switch (op) {
788
+ case EW_EQEQ:
789
+ tmp_result = (uint8_t)(*reinterpret_cast<LDType*>(l_node->val) == r_default);
790
+ break;
791
+
792
+ case EW_NEQ:
793
+ tmp_result = (uint8_t)(*reinterpret_cast<LDType*>(l_node->val) != r_default);
794
+ break;
795
+
796
+ case EW_LT:
797
+ tmp_result = (uint8_t)(*reinterpret_cast<LDType*>(l_node->val) < r_default);
798
+ break;
799
+
800
+ case EW_GT:
801
+ tmp_result = (uint8_t)(*reinterpret_cast<LDType*>(l_node->val) > r_default);
802
+ break;
803
+
804
+ case EW_LEQ:
805
+ tmp_result = (uint8_t)(*reinterpret_cast<LDType*>(l_node->val) <= r_default);
806
+ break;
807
+
808
+ case EW_GEQ:
809
+ tmp_result = (uint8_t)(*reinterpret_cast<LDType*>(l_node->val) >= r_default);
810
+ break;
811
+
812
+ default:
813
+ rb_raise(rb_eStandardError, "this should not happen");
814
+ }
815
+
816
+ if (tmp_result != d_default) {
817
+ dest_node = nm::list::insert_helper(dest, dest_node, index, tmp_result);
818
+ }
819
+
820
+ } else {
821
+ new_level = nm::list::create();
822
+ dest_node = nm::list::insert_helper(dest, dest_node, index, new_level);
823
+
824
+ ew_comp_prime<op, LDType, RDType>(new_level, d_default,
825
+ reinterpret_cast<LIST*>(l_node->val), l_default,
826
+ &EMPTY_LIST, r_default,
827
+ shape, last_level, level + 1);
828
+ }
829
+
830
+ l_node = l_node->next;
831
+
832
+ } else if (l_node != NULL and r_node != NULL and index == std::min(l_node->key, r_node->key)) {
833
+ /*
834
+ * Neither list is empty and our index has caught up to one of the
835
+ * source lists.
836
+ */
837
+
838
+ if (l_node->key == r_node->key) {
839
+
840
+ if (level == last_level) {
841
+ switch (op) {
842
+ case EW_EQEQ:
843
+ tmp_result = (uint8_t)(*reinterpret_cast<LDType*>(l_node->val) == *reinterpret_cast<RDType*>(r_node->val));
844
+ break;
845
+
846
+ case EW_NEQ:
847
+ tmp_result = (uint8_t)(*reinterpret_cast<LDType*>(l_node->val) != *reinterpret_cast<RDType*>(r_node->val));
848
+ break;
849
+
850
+ case EW_LT:
851
+ tmp_result = (uint8_t)(*reinterpret_cast<LDType*>(l_node->val) < *reinterpret_cast<RDType*>(r_node->val));
852
+ break;
853
+
854
+ case EW_GT:
855
+ tmp_result = (uint8_t)(*reinterpret_cast<LDType*>(l_node->val) > *reinterpret_cast<RDType*>(r_node->val));
856
+ break;
857
+
858
+ case EW_LEQ:
859
+ tmp_result = (uint8_t)(*reinterpret_cast<LDType*>(l_node->val) <= *reinterpret_cast<RDType*>(r_node->val));
860
+ break;
861
+
862
+ case EW_GEQ:
863
+ tmp_result = (uint8_t)(*reinterpret_cast<LDType*>(l_node->val) >= *reinterpret_cast<RDType*>(r_node->val));
864
+ break;
865
+
866
+ default:
867
+ rb_raise(rb_eStandardError, "this should not happen");
868
+ }
869
+
870
+ if (tmp_result != d_default) {
871
+ dest_node = nm::list::insert_helper(dest, dest_node, index, tmp_result);
872
+ }
873
+
874
+ } else {
875
+ new_level = nm::list::create();
876
+ dest_node = nm::list::insert_helper(dest, dest_node, index, new_level);
877
+
878
+ ew_comp_prime<op, LDType, RDType>(new_level, d_default,
879
+ reinterpret_cast<LIST*>(l_node->val), l_default,
880
+ reinterpret_cast<LIST*>(r_node->val), r_default,
881
+ shape, last_level, level + 1);
882
+ }
883
+
884
+ l_node = l_node->next;
885
+ r_node = r_node->next;
886
+
887
+ } else if (l_node->key < r_node->key) {
888
+ // Advance the left node knowing that the default value is OK.
889
+
890
+ l_node = l_node->next;
891
+
892
+ } else /* if (l_node->key > r_node->key) */ {
893
+ // Advance the right node knowing that the default value is OK.
894
+
895
+ r_node = r_node->next;
896
+ }
897
+
898
+ } else {
899
+ /*
900
+ * Our index needs to catch up but the default value is OK. This
901
+ * conditional is here only for documentation and should be optimized
902
+ * out.
903
+ */
904
+ }
905
+ }
906
+ }
907
+ }
908
+
909
+
910
+
911
+ /*
912
+ * List storage element-wise operations, recursive helper.
913
+ */
914
+ template <ewop_t op, typename LDType, typename RDType>
915
+ static void ew_op_prime(LIST* dest, LDType d_default, const LIST* left, LDType l_default, const LIST* right, RDType r_default, const size_t* shape, size_t last_level, size_t level) {
916
+
917
+ static LIST EMPTY_LIST = {NULL};
918
+
919
+ size_t index;
920
+
921
+ LDType tmp_result;
922
+
923
+ LIST* new_level = NULL;
924
+
925
+ NODE* l_node = left->first,
926
+ * r_node = right->first,
927
+ * dest_node = NULL;
928
+
929
+ for (index = 0; index < shape[level]; ++index) {
930
+ if (l_node == NULL and r_node == NULL) {
931
+ /*
932
+ * Both source lists are now empty. Because the default value of the
933
+ * destination is already set appropriately we can now return.
934
+ */
935
+
936
+ return;
937
+
938
+ } else {
939
+ // At least one list still has entries.
940
+
941
+ if (op == EW_MUL) {
942
+ // Special cases for multiplication.
943
+
944
+ if (l_node == NULL and (l_default == 0 and d_default == 0)) {
945
+ /*
946
+ * The left hand list has run out of elements. We don't need to add new
947
+ * values to the destination if l_default and d_default are both 0.
948
+ */
949
+
950
+ return;
951
+
952
+ } else if (r_node == NULL and (r_default == 0 and d_default == 0)) {
953
+ /*
954
+ * The right hand list has run out of elements. We don't need to add new
955
+ * values to the destination if r_default and d_default are both 0.
956
+ */
957
+
958
+ return;
959
+ }
960
+
961
+ } else if (op == EW_DIV) {
962
+ // Special cases for division.
963
+
964
+ if (l_node == NULL and (l_default == 0 and d_default == 0)) {
965
+ /*
966
+ * The left hand list has run out of elements. We don't need to add new
967
+ * values to the destination if l_default and d_default are both 0.
968
+ */
969
+
970
+ return;
971
+
972
+ } else if (r_node == NULL and (r_default == 0 and d_default == 0)) {
973
+ /*
974
+ * The right hand list has run out of elements. If the r_default
975
+ * value is 0 any further division will result in a SIGFPE.
976
+ */
977
+
978
+ rb_raise(rb_eZeroDivError, "Cannot divide type by 0, would throw SIGFPE.");
979
+ }
980
+
981
+ // TODO: Add optimizations for addition and subtraction.
982
+
983
+ }
984
+
985
+ // We need to continue processing the lists.
986
+
987
+ if (l_node == NULL and r_node->key == index) {
988
+ /*
989
+ * One source list is empty, but the index has caught up to the key of
990
+ * the other list.
991
+ */
992
+
993
+ if (level == last_level) {
994
+ tmp_result = ew_op_switch<op, LDType, RDType>(l_default, *reinterpret_cast<RDType*>(r_node->val));
995
+ std::cerr << "1. tmp_result = " << tmp_result << std::endl;
996
+
997
+ if (tmp_result != d_default) {
998
+ dest_node = nm::list::insert_helper(dest, dest_node, index, tmp_result);
999
+ }
1000
+
1001
+ } else {
1002
+ new_level = nm::list::create();
1003
+ dest_node = nm::list::insert_helper(dest, dest_node, index, new_level);
1004
+
1005
+ ew_op_prime<op, LDType, RDType>(new_level, d_default, &EMPTY_LIST, l_default,
1006
+ reinterpret_cast<LIST*>(r_node->val), r_default,
1007
+ shape, last_level, level + 1);
1008
+ }
1009
+
1010
+ r_node = r_node->next;
1011
+
1012
+ } else if (r_node == NULL and l_node->key == index) {
1013
+ /*
1014
+ * One source list is empty, but the index has caught up to the key of
1015
+ * the other list.
1016
+ */
1017
+
1018
+ if (level == last_level) {
1019
+ tmp_result = ew_op_switch<op, LDType, RDType>(*reinterpret_cast<LDType*>(l_node->val), r_default);
1020
+ std::cerr << "2. tmp_result = " << tmp_result << std::endl;
1021
+
1022
+ if (tmp_result != d_default) {
1023
+ dest_node = nm::list::insert_helper(dest, dest_node, index, tmp_result);
1024
+ }
1025
+
1026
+ } else {
1027
+ new_level = nm::list::create();
1028
+ dest_node = nm::list::insert_helper(dest, dest_node, index, new_level);
1029
+
1030
+ ew_op_prime<op, LDType, RDType>(new_level, d_default, reinterpret_cast<LIST*>(l_node->val), l_default,
1031
+ &EMPTY_LIST, r_default, shape, last_level, level + 1);
1032
+ }
1033
+
1034
+ l_node = l_node->next;
1035
+
1036
+ } else if (l_node != NULL and r_node != NULL and index == std::min(l_node->key, r_node->key)) {
1037
+ /*
1038
+ * Neither list is empty and our index has caught up to one of the
1039
+ * source lists.
1040
+ */
1041
+
1042
+ if (l_node->key == r_node->key) {
1043
+
1044
+ if (level == last_level) {
1045
+ tmp_result = ew_op_switch<op, LDType, RDType>(*reinterpret_cast<LDType*>(l_node->val),*reinterpret_cast<RDType*>(r_node->val));
1046
+ std::cerr << "3. tmp_result = " << tmp_result << std::endl;
1047
+
1048
+ if (tmp_result != d_default) {
1049
+ dest_node = nm::list::insert_helper(dest, dest_node, index, tmp_result);
1050
+ }
1051
+
1052
+ } else {
1053
+ new_level = nm::list::create();
1054
+ dest_node = nm::list::insert_helper(dest, dest_node, index, new_level);
1055
+
1056
+ ew_op_prime<op, LDType, RDType>(new_level, d_default,
1057
+ reinterpret_cast<LIST*>(l_node->val), l_default,
1058
+ reinterpret_cast<LIST*>(r_node->val), r_default,
1059
+ shape, last_level, level + 1);
1060
+ }
1061
+
1062
+ l_node = l_node->next;
1063
+ r_node = r_node->next;
1064
+
1065
+ } else if (l_node->key < r_node->key) {
1066
+ // Advance the left node knowing that the default value is OK.
1067
+
1068
+ l_node = l_node->next;
1069
+
1070
+ } else /* if (l_node->key > r_node->key) */ {
1071
+ // Advance the right node knowing that the default value is OK.
1072
+
1073
+ r_node = r_node->next;
1074
+ }
1075
+
1076
+ } else {
1077
+ /*
1078
+ * Our index needs to catch up but the default value is OK. This
1079
+ * conditional is here only for documentation and should be optimized
1080
+ * out.
1081
+ */
1082
+ }
1083
+ }
1084
+ }
1085
+ }
1086
+
1087
+ }} // end of namespace nm::list_storage
1088
+