nmatrix 0.0.1

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 (43) hide show
  1. data/.autotest +23 -0
  2. data/.gemtest +0 -0
  3. data/Gemfile +7 -0
  4. data/History.txt +6 -0
  5. data/LICENSE.txt +21 -0
  6. data/Manifest.txt +51 -0
  7. data/README.rdoc +63 -0
  8. data/Rakefile +154 -0
  9. data/ext/nmatrix/cblas.c +150 -0
  10. data/ext/nmatrix/dense.c +307 -0
  11. data/ext/nmatrix/dense/blas_header.template.c +52 -0
  12. data/ext/nmatrix/dense/elementwise.template.c +107 -0
  13. data/ext/nmatrix/dense/gemm.template.c +159 -0
  14. data/ext/nmatrix/dense/gemv.template.c +130 -0
  15. data/ext/nmatrix/dense/rationalmath.template.c +68 -0
  16. data/ext/nmatrix/depend +18 -0
  17. data/ext/nmatrix/extconf.rb +143 -0
  18. data/ext/nmatrix/generator.rb +594 -0
  19. data/ext/nmatrix/generator/syntax_tree.rb +481 -0
  20. data/ext/nmatrix/list.c +774 -0
  21. data/ext/nmatrix/nmatrix.c +1977 -0
  22. data/ext/nmatrix/nmatrix.h +912 -0
  23. data/ext/nmatrix/rational.c +98 -0
  24. data/ext/nmatrix/yale.c +726 -0
  25. data/ext/nmatrix/yale/complexmath.template.c +71 -0
  26. data/ext/nmatrix/yale/elementwise.template.c +46 -0
  27. data/ext/nmatrix/yale/elementwise_op.template.c +73 -0
  28. data/ext/nmatrix/yale/numbmm.template.c +94 -0
  29. data/ext/nmatrix/yale/smmp1.template.c +21 -0
  30. data/ext/nmatrix/yale/smmp1_header.template.c +38 -0
  31. data/ext/nmatrix/yale/smmp2.template.c +43 -0
  32. data/ext/nmatrix/yale/smmp2_header.template.c +46 -0
  33. data/ext/nmatrix/yale/sort_columns.template.c +56 -0
  34. data/ext/nmatrix/yale/symbmm.template.c +54 -0
  35. data/ext/nmatrix/yale/transp.template.c +68 -0
  36. data/lib/array.rb +67 -0
  37. data/lib/nmatrix.rb +263 -0
  38. data/lib/string.rb +65 -0
  39. data/spec/nmatrix_spec.rb +395 -0
  40. data/spec/nmatrix_yale_spec.rb +239 -0
  41. data/spec/nvector_spec.rb +43 -0
  42. data/spec/syntax_tree_spec.rb +46 -0
  43. metadata +150 -0
@@ -0,0 +1,481 @@
1
+ # = NMatrix
2
+ #
3
+ # A linear algebra library for scientific computation in Ruby.
4
+ # NMatrix is part of SciRuby.
5
+ #
6
+ # NMatrix was originally inspired by and derived from NArray, by
7
+ # Masahiro Tanaka: http://narray.rubyforge.org
8
+ #
9
+ # == Copyright Information
10
+ #
11
+ # SciRuby is Copyright (c) 2010 - 2012, Ruby Science Foundation
12
+ # NMatrix is Copyright (c) 2012, Ruby Science Foundation
13
+ #
14
+ # Please see LICENSE.txt for additional copyright notices.
15
+ #
16
+ # == Contributing
17
+ #
18
+ # By contributing source code to SciRuby, you agree to be bound by
19
+ # our Contributor Agreement:
20
+ #
21
+ # * https://github.com/SciRuby/sciruby/wiki/Contributor-Agreement
22
+ #
23
+ # == syntax_tree.rb
24
+ #
25
+ # Syntax Tree for mathematical expression parsing. Produces the
26
+ # correct forms for each type in NMatrix. Used by the C templates
27
+ # in generator.rb.
28
+ #
29
+
30
+ class Identifier < String
31
+ def operate type=nil,exact_type=nil
32
+ [type == :value || type == :object && to_s == "0" ? "RUBY_ZERO" : to_s]
33
+ end
34
+
35
+ def depth; 0; end
36
+
37
+ # These are just dummy functions which alias to_s
38
+
39
+ def operate_complex exact_type=nil
40
+ [to_s]
41
+ end
42
+
43
+ def operate_rational exact_type=nil
44
+ [to_s]
45
+ end
46
+
47
+ def operate_value exact_type=nil
48
+ [to_s]
49
+ end
50
+ end
51
+
52
+
53
+ class SyntaxTree
54
+ EQ = :'='
55
+ # Changing the order of these arrays will change the relative precedence of the operators.
56
+ ASSIGN_OPS = %w{+= -= *= /= %= &= ^= |= >>= <<=}.map { |o| o.intern }
57
+ COMP_OPS = %w{<= >= == != < >}.map { |o| o.intern }
58
+ TRANSITIVE_BINARY_OPS = %w{&& || & ~ + *}.map { |o| o.intern }
59
+ BINARY_OPS = %w{>> << && || & | ^ - + * / %}.map { |o| o.intern }
60
+ BITWISE_BINARY_OPS = %w{& | ^}.map { |o| o.intern }
61
+ UNARY_OPS = %w{~ - !}
62
+ DISALLOWED_OPS = %w{++ --}.map { |o| o.intern }
63
+ OPS = (%w{>> <<}.concat(ASSIGN_OPS + [EQ]).concat(COMP_OPS).concat(%w{- + * / % & | ^}).concat(UNARY_OPS)).map { |o| o.intern } # skipping: >> << && || & | ~
64
+
65
+ FLIP_OPS = {:'<=' => :'>=',
66
+ :'>=' => :'<=',
67
+ :'==' => :'==',
68
+ :'!=' => :'!=',
69
+ :'>' => :'<',
70
+ :'<' => :'>' }
71
+
72
+ # Generally this is only used internally. Instead, call SyntaxTree.parse(operation), which will
73
+ # in turn correctly call SyntaxTree::initialize.
74
+ def initialize op, left, right
75
+ if left == "0" && COMP_OPS.include?(op) # Flip certain operations so that the 0 is on the right.
76
+ @op = FLIP_OPS[op]
77
+ @left = right
78
+ @right = left
79
+ elsif left.is_a?(String) && left.size == 0
80
+ @op = op
81
+ @left = nil
82
+ @right = right
83
+ else
84
+ @op = op
85
+ @left = left
86
+ @right = right
87
+ end
88
+ end
89
+ attr_reader :op
90
+ attr_accessor :left, :right
91
+
92
+
93
+ def unary?
94
+ left.nil?
95
+ end
96
+
97
+
98
+ def is_boolean?
99
+ COMP_OPS.include?(op)
100
+ end
101
+
102
+
103
+ # Flip an operation, if possible. If not, do nothing.
104
+ def reverse!
105
+ if COMP_OPS.include?(op)
106
+ @op = FLIP_OPS[op]
107
+ tmp = @left
108
+ @left = @right
109
+ @right = tmp
110
+ elsif TRANSITIVE_BINARY_OPS.include?(op)
111
+ tmp = @left
112
+ @left = @right
113
+ @right = tmp
114
+ end
115
+ end
116
+
117
+ # Flip an operation
118
+ def reverse
119
+ self.dup.reverse!
120
+ end
121
+
122
+
123
+ # Expand the syntax tree in place on ASSIGN_OPS. If @op is not one of the ASSIGN_OPS, do nothing.
124
+ def expand!
125
+ if ASSIGN_OPS.include?(@op)
126
+ old_op = @op
127
+ @op = EQ
128
+ old_right = @right
129
+ @right = SyntaxTree.new(old_op.to_s[0...old_op.size-1].intern, @left, old_right)
130
+ end
131
+ self
132
+ end
133
+
134
+
135
+ # Copy and expand the syntax tree on ASSIGN_OPS. e.g., += becomes = and +
136
+ def expand
137
+ self.dup.expand!
138
+ end
139
+
140
+
141
+ def depth
142
+ unary? ? right.depth : (left.depth > right.depth ? left.depth : right.depth) + 1
143
+ end
144
+
145
+ # Split the SyntaxTree into a whole bunch of simpler ones appropriate for doing struct-type operations.
146
+ def simplify_split! count=1
147
+ expand!
148
+ ary = [self]
149
+ if op == EQ
150
+ if depth <= 2
151
+ ary
152
+ else
153
+ ary
154
+ if right.left.is_a?(SyntaxTree)
155
+ old_right_left = right.left
156
+ right.left = Identifier.new("temp#{count}")
157
+ new_right_left = SyntaxTree.new(EQ, right.left, old_right_left)
158
+ count += 1
159
+ ary = ary.concat(new_right_left.simplify_split!(count))
160
+ end
161
+
162
+ if right.right.is_a?(SyntaxTree)
163
+ old_right_right = right.right
164
+ right.right = Identifier.new("temp#{count}")
165
+ new_right_right = SyntaxTree.new(EQ, right.right, old_right_right)
166
+ count += 1
167
+ ary = ary.concat(new_right_right.simplify_split!(count))
168
+ end
169
+
170
+ ary.reverse
171
+ end
172
+ else
173
+ ary
174
+ end
175
+ end
176
+
177
+ def simplify_split count=1
178
+ dup.simplify_split! count
179
+ end
180
+
181
+
182
+ # Deep copy.
183
+ def dup
184
+ SyntaxTree.new(op, (unary? ? nil : left.dup), right.dup)
185
+ end
186
+
187
+
188
+ # Split tree into multiple simpler subtrees and figure out the appropriate operation on each of those.
189
+ #
190
+ # Only needed for rational and complex, which are structs and can't rely on basic operators like * and +.
191
+ def operate_on_subtrees type, exact_type
192
+ i = 0
193
+ best_type = begin
194
+ case type
195
+ when :rational
196
+ :r128
197
+ when :complex
198
+ :c128
199
+ when :object
200
+ :v
201
+ end
202
+ end
203
+ subtrees = simplify_split
204
+ subtrees.map do |subtree|
205
+ i += 1
206
+ i == subtrees.size ? subtree.send("operate_#{type}", exact_type) : subtree.send("operate_#{type}", best_type)
207
+ end
208
+ end
209
+
210
+
211
+ def operate type, exact_type=nil
212
+ operations =
213
+ case type
214
+ when :int, :float
215
+ operate_simple(type, exact_type)
216
+ when :rational, :complex, :value
217
+ operate_on_subtrees(type, exact_type).flatten
218
+ else
219
+ raise ArgumentError, "undefined math expression type '#{type}'"
220
+ end
221
+
222
+ # Separate boolean operations by && or ; as appropriate for the type of operation.
223
+ # This is overly simplistic, and won't work for things like x = y && z; but, it will
224
+ # work for the majority of cases we need to deal with in our templates.
225
+ #COMP_OPS.include?(op) ? operations.join(" && ") : operations.join(";\n") + ";"
226
+ operations
227
+ end
228
+
229
+
230
+ protected
231
+ def is_expression_boolean? expr
232
+ expr =~ /^\(.*(==|!=|>=|<=| < | > |&&|\|\|)+.*\)$/ || expr =~ /^!/
233
+ end
234
+
235
+
236
+ def operate_simple type, exact_type
237
+ if unary?
238
+ if op == :'~' && type == :float
239
+ ["rb_raise(rb_eSyntaxError, \"cannot perform bitwise operations on floats\")"]
240
+ else
241
+ ["#{op}#{right.operate(type, exact_type).first}"]
242
+ end
243
+ elsif op == EQ
244
+ r = right.operate(type, exact_type).first
245
+ r =~ /^rb_raise\(rb_eSyntaxError/ ? [r] : ["#{left} = #{r}"]
246
+ elsif op == :'%' && type == :float
247
+ ["fmod(#{left.operate(type, exact_type).first}, #{right.operate(type, exact_type).first})"]
248
+ elsif [:'&', :'|', :'^', :'<<', :'>>'].include?(op)
249
+ ["rb_raise(rb_eSyntaxError, \"cannot perform bitwise operations on floats\")"]
250
+ else
251
+ ["#{left.operate(type, exact_type).first} #{op} #{right.operate(type, exact_type).first}"]
252
+ end
253
+ end
254
+
255
+ # Figure out what to do on the right side of the operation if we're dealing with Ruby objects and unary operators
256
+ def operate_value_right
257
+ if !right.is_a?(SyntaxTree) && right.to_i.to_s == right
258
+ unary? ? "INT2FIX(#{op}#{right})" : "INT2FIX(#{right})"
259
+ elsif right.is_a?(SyntaxTree) && right.unary? && right.right.to_i.to_s == right.right
260
+ "INT2FIX(#{op}#{right})"
261
+ else
262
+ right.operate_value.first
263
+ end
264
+ end
265
+
266
+ def operate_value exact_type=nil # exact_type is not used.
267
+ if unary?
268
+ ["rb_funcall(#{operate_value_right}, rb_intern(\"#{op}@\"), 0)"] # e.g., -@ for negative
269
+ else
270
+ case op
271
+ when EQ
272
+ ["#{left} #{op} #{operate_value_right}"]
273
+ when *COMP_OPS
274
+ ["rb_funcall(#{left.operate_value.first}, rb_intern(\"#{op}\"), 1, #{operate_value_right}) == Qtrue"]
275
+ else
276
+ ["rb_funcall(#{left.operate_value.first}, rb_intern(\"#{op}\"), 1, #{operate_value_right})"]
277
+ end
278
+ end
279
+ end
280
+
281
+ def operate_rational exact_type=:r128
282
+ if unary?
283
+ if op == :'-'
284
+ ["#{exact_type}_negate(#{right}.n, #{right}.d)"]
285
+ elsif op == :'~'
286
+ ["rb_raise(rb_eSyntaxError, \"cannot perform bitwise operations on rational numbers\")"]
287
+ elsif op == :'!'
288
+ ["#{exact_type}_bang(#{right}.n, #{right}.d)"]
289
+ else
290
+ raise NotImplementedError, "unhandled unary operation #{op} on complex and 0"
291
+ end
292
+ elsif right == "0"
293
+ if op == EQ
294
+ ["#{left}.n = 0", " #{left}.d = 1"]
295
+ elsif COMP_OPS.include?(op)
296
+ ["#{left}.n #{op} 0"]
297
+ else
298
+ raise NotImplementedError, "unhandled operation #{op} on rational and 0"
299
+ end
300
+ elsif right == "1"
301
+ if op == EQ
302
+ ["#{left}.n = #{left}.d = 1"]
303
+ elsif COMP_OPS.include?(op)
304
+ ["#{left}.n #{op} #{left}.d"]
305
+ elsif [:'+', :'-'].include?(op)
306
+ ["#{exact_type}_addsub(#{left}.n, #{left}.d, #{left}.d, #{left}.d, '#{op}')"]
307
+ else
308
+ raise NotImplementedError, "unhandled operation #{op} on rational and 1"
309
+ end
310
+ else
311
+ if op == EQ
312
+ r = right.operate_rational(exact_type).first
313
+ if r =~ /^rb_raise\(rb_eSyntaxError/
314
+ [r]
315
+ elsif is_expression_boolean?(r) # x = boolean: can't assign directly.
316
+ ["#{left} = BOOL2#{exact_type.to_s.upcase}(#{r})"]
317
+ else
318
+ ["#{left} = #{right.operate_rational(exact_type).first}"]
319
+ end
320
+ elsif op == :'=='
321
+ ["(#{left}.n == #{right}.n && #{left}.d == #{right}.d)"]
322
+ elsif COMP_OPS.include?(op)
323
+ ["(#{left}.n / (double)(#{left}.d) #{op} #{right}.n / (double)(#{right}.d))"] # TODO: Is there a faster way?
324
+ elsif [:'+', :'-'].include?(op)
325
+ ["#{exact_type}_addsub(#{left}.n, #{left}.d, #{right}.n, #{right}.d, '#{op}')"]
326
+ elsif [:'*', :'/'].include?(op)
327
+ ["#{exact_type}_muldiv(#{left}.n, #{left}.d, #{right}.n, #{right}.d, '#{op}')"]
328
+ elsif op == :'%'
329
+ ["#{exact_type}_mod(#{left}.n, #{left}.d, #{right}.n, #{right}.d)"]
330
+ elsif op == :'<<'
331
+ ["rb_raise(rb_eSyntaxError, \"cannot perform bitwise operations on rational numbers\")"]
332
+ elsif op == :'>>'
333
+ ["rb_raise(rb_eSyntaxError, \"cannot perform bitwise operations on rational numbers\")"]
334
+ elsif BITWISE_BINARY_OPS.include?(op)
335
+ ["rb_raise(rb_eSyntaxError, \"cannot perform bitwise operations on rational numbers\")"]
336
+ else
337
+ raise NotImplementedError, "unhandled operation #{op} on rationals"
338
+ end
339
+ end
340
+ end
341
+
342
+
343
+ def operate_complex_boolean_helper exact_type, real_comp, join = :'&&', imag_comp=nil
344
+ imag_comp ||= real_comp
345
+ ["(#{left.operate_complex(exact_type).first}.r #{real_comp} #{right.operate_complex(exact_type).first}.r && #{left.operate_complex(exact_type).first}.i #{imag_comp} #{right.operate_complex(exact_type).first}.i)"]
346
+ end
347
+
348
+
349
+ def operate_complex exact_type=:c128
350
+ if unary?
351
+ if op == :'-'
352
+ ["#{exact_type}_negate(#{right}.r, #{right}.i)"]
353
+ elsif op == :'~'
354
+ ["rb_raise(rb_eSyntaxError, \"cannot perform bitwise operations on complex numbers\")"]
355
+ elsif op == :'!'
356
+ ["#{exact_type}_bang(#{right}.r, #{right}.i)"]
357
+ else
358
+ raise NotImplementedError, "unhandled unary operation #{op} on complex and 0"
359
+ end
360
+ elsif right == "0"
361
+ if op == EQ
362
+ ["#{left}.r = 0", " #{left}.i = 0"]
363
+ elsif op == :'=='
364
+ ["(#{left}.r == 0 && #{left}.i == 0)"]
365
+ elsif op == :'!='
366
+ ["(#{left}.r != 0 || #{left}.i != 0)"]
367
+ elsif op == :'>'
368
+ ["(#{left}.r > 0 && #{left}.i > 0)"]
369
+ elsif op == :'<'
370
+ ["(#{left}.r < 0 && #{left}.i < 0)"]
371
+ elsif op == :'>='
372
+ ["(!(#{left}.r < 0 || #{left}.i < 0)"]
373
+ elsif op == :'<='
374
+ ["(!(#{left}.r > 0 || #{left}.i > 0)"]
375
+ else
376
+ raise NotImplementedError, "unhandled operation #{op} on complex and 0"
377
+ end
378
+ else
379
+ if op == EQ
380
+ r = right.operate_complex(exact_type).first
381
+ if r =~ /^rb_raise\(rb_eSyntaxError/
382
+ [r]
383
+ elsif is_expression_boolean?(r) # x = boolean: can't assign directly.
384
+ ["#{left} = BOOL2#{exact_type.to_s.upcase}(#{r})"]
385
+ else
386
+ ["#{left} = #{r}"]
387
+ end
388
+ elsif op == :'=='
389
+ operate_complex_boolean_helper(exact_type, op)
390
+ elsif op == :'!='
391
+ operate_complex_boolean_helper(exact_type, op, :'||')
392
+ elsif op == :'>'
393
+ operate_complex_boolean_helper(exact_type, op)
394
+ elsif op == :'<'
395
+ operate_complex_boolean_helper(exact_type, op)
396
+ elsif op == :'>='
397
+ operate_complex_boolean_helper(exact_type, op)
398
+ elsif op == :'<='
399
+ operate_complex_boolean_helper(exact_type, op)
400
+ elsif op == :'+'
401
+ ["#{exact_type}_add(#{left}.r, #{left}.i, #{right}.r, #{right}.i)"]
402
+ elsif op == :'-'
403
+ ["#{exact_type}_sub(#{left}.r, #{left}.i, #{right}.r, #{right}.i)"]
404
+ elsif op == :'*'
405
+ ["#{exact_type}_mul(#{left}.r, #{left}.i, #{right}.r, #{right}.i)"]
406
+ elsif op == :'/'
407
+ ["#{exact_type}_div(#{left}.r, #{left}.i, #{right}.r, #{right}.i)"]
408
+ elsif op == :'%'
409
+ ["#{exact_type}_mod(#{left}.r, #{left}.i, #{right}.r, #{right}.i)"]
410
+ elsif [:'&', :'|', :'^', :'<<', :'>>'].include?(op)
411
+ ["rb_raise(rb_eSyntaxError, \"cannot perform bitwise operations on complex numbers\")"]
412
+ elsif op == :'!'
413
+ ["0"]
414
+ else
415
+ STDERR.puts "WARNING: unhandled operation #{op} on complex numbers"
416
+ ["0"]
417
+ end
418
+ end
419
+ end
420
+
421
+ public
422
+ class << self
423
+
424
+ # Parse an operation template of some kind into a SyntaxTree or, if no operation present,
425
+ # into an Identifier (basically a string)
426
+ def parse str
427
+ SyntaxTree::OPS.each do |op_symbol|
428
+ op = op_symbol.to_s
429
+ pos = str.index(op, 1)
430
+
431
+ while !pos.nil? # Need to look for each operator multiple times in a line
432
+
433
+ raise(ArgumentError, "disallowed operation '#{op}' in expression") if DISALLOWED_OPS.include?(op_symbol)
434
+
435
+ # Is the operator contained within brackets?
436
+ lb = str.rindex('[', pos)
437
+ unless lb.nil?
438
+ rb = str.rindex(']', pos) # Perhaps that bracket is terminated?
439
+ rb = str.index(']', pos+op.size) if rb.nil? || rb < lb # still have to worry; try looking after the operator
440
+
441
+ if BINARY_OPS.include?(op_symbol) && !rb.nil? && lb < pos && rb >= pos
442
+ # Operator IS within brackets. Let's start searching again after the brackets.
443
+ #STDERR.puts "operator #{op_symbol} was found, but within brackets (#{lb}, #{pos}, #{rb}) '#{str}'"
444
+ pos = str.index(op, rb+1)
445
+ next
446
+ end
447
+ end
448
+
449
+ # If we get this far, the operator was not found within brackets.
450
+
451
+ # Split around the operator
452
+ left = str[0...pos].strip
453
+ right = str[pos+op.size...str.size].strip
454
+
455
+ # We don't want to process x <= y as 'x <' = 'y', but as 'x' <= 'y':
456
+ if op_symbol == EQ
457
+ if SyntaxTree::OPS.include?((left[-1]+'=').intern)
458
+ pos = str.index(op, pos+1)
459
+ next
460
+ elsif SyntaxTree::OPS.include?(('=' + right[0]).intern) # Same for ==
461
+ pos = str.index(op, pos+2)
462
+ next
463
+ end
464
+ end
465
+
466
+ STDERR.puts "operator #{op_symbol} was found (#{pos}); parsing '#{left}' and '#{right}'"
467
+ return SyntaxTree.new(op_symbol, SyntaxTree.parse(left), SyntaxTree.parse(right))
468
+
469
+ end
470
+ end
471
+
472
+ if str =~ /[~\-!]/
473
+ STDERR.puts "unary operator #{str[0]} was found (0); making identifier of #{str[1...str.size]}"
474
+ SyntaxTree.new(str[0].intern, "", Identifier.new(str[1...str.size]))
475
+ else
476
+ STDERR.puts "Making identifier out of #{str}"
477
+ Identifier.new(str)
478
+ end
479
+ end
480
+ end
481
+ end