nmatrix 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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,594 @@
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
+ # == generator.rb
24
+ #
25
+ # Module for generating source files.
26
+
27
+ $RELATIVE_PATH = nil
28
+
29
+ $IN_MAKEFILE = begin
30
+ dir_pwd_split = Dir.pwd.split('/')
31
+ if dir_pwd_split.size >= 4 && dir_pwd_split[-4] == "tmp" # when running make by hand
32
+ $RELATIVE_PATH = "../../../../"
33
+ true
34
+ elsif dir_pwd_split[-2] == "ext" # when building gem
35
+ $RELATIVE_PATH = File.join(File.dirname(__FILE__), "../..")
36
+ true
37
+ else # when building in development dir
38
+ $RELATIVE_PATH = File.dirname(__FILE__)
39
+ false
40
+ end
41
+ end
42
+
43
+ require File.join($RELATIVE_PATH, "lib/string.rb") # from the Makefile
44
+ require File.join($RELATIVE_PATH, "ext/nmatrix/generator/syntax_tree.rb")
45
+
46
+ class DTypeInfo < Struct.new(:enum, :sizeof, :sym, :id, :type)
47
+ def max_macro
48
+ typename = self.sizeof.to_s
49
+ if typename.include?('_')
50
+ ary = typename.split('_')
51
+ typename = ary[0...ary.size-1].join('')
52
+ end
53
+ typename.upcase + "_MAX"
54
+ end
55
+
56
+ def min_macro
57
+ typename = self.sizeof.to_s
58
+ if typename.include?('_')
59
+ ary = typename.split('_')
60
+ typename = ary[0...ary.size-1].join('')
61
+ end
62
+ typename.upcase + "_MIN"
63
+ end
64
+
65
+ # What type would this be if we used the maximum number of bytes available?
66
+ def long_dtype
67
+ Generator::DTYPES.select { |x| x.type == self.type }.last
68
+ end
69
+ end
70
+
71
+
72
+ class Array
73
+ def max
74
+ found_max = nil
75
+ self.each_index do |i|
76
+ found_max = self[i] if found_max.nil? || self[i] > found_max
77
+ end
78
+ found_max
79
+ end
80
+
81
+ def min
82
+ found_min = nil
83
+ self.each_index do |i|
84
+ found_min = self[i] if found_min.nil? || self[i] < found_min
85
+ end
86
+ found_min
87
+ end
88
+ end
89
+
90
+
91
+ module Generator
92
+ SRC_DIR = File.join("ext", "nmatrix")
93
+ DTYPES = [
94
+ # enum sizeof sym id type
95
+ [:NM_NONE, 0, :none, 0, :none],
96
+ [:NM_BYTE, :u_int8_t, :byte, :b, :int],
97
+ [:NM_INT8, :int8_t, :int8, :i8, :int],
98
+ [:NM_INT16, :int16_t, :int16, :i16, :int],
99
+ [:NM_INT32, :int32_t, :int32, :i32, :int],
100
+ [:NM_INT64, :int64_t, :int64, :i64, :int],
101
+ [:NM_FLOAT32, :float, :float32, :f32, :float],
102
+ [:NM_FLOAT64, :double, :float64, :f64, :float],
103
+ [:NM_COMPLEX64, :complex64, :complex64, :c64, :complex],
104
+ [:NM_COMPLEX128, :complex128, :complex128, :c128, :complex],
105
+ [:NM_RATIONAL32, :rational32, :rational32, :r32, :rational],
106
+ [:NM_RATIONAL64, :rational64, :rational64, :r64, :rational],
107
+ [:NM_RATIONAL128, :rational128, :rational128, :r128, :rational],
108
+ [:NM_ROBJ, :VALUE, :object, :v, :value],
109
+ [:NM_TYPES, 0, :dtypes, 0, :none]
110
+ ].map { |d| DTypeInfo.new(*d) }
111
+
112
+ INDEX_DTYPES = DTYPES.select { |dtype| dtype.type == :int && dtype.id != :b }
113
+ INTEGER_DTYPES = DTYPES.select { |dtype| dtype.type == :int }
114
+ RATIONAL_DTYPES = DTYPES.select { |dtype| dtype.type == :rational }
115
+ NONBLAS_DTYPES = DTYPES.select { |dtype| [:int,:rational,:value].include?(dtype.type) }
116
+ COMPLEX_DTYPES = DTYPES.select { |dtype| dtype.type == :complex }
117
+ FLOAT_DTYPES = DTYPES.select { |dtype| dtype.type == :float }
118
+ OBJECT_DTYPES = DTYPES.select { |dtype| dtype.type == :value }
119
+ ACTUAL_DTYPES = DTYPES.select { |dtype| dtype.type != :none }
120
+
121
+ YIELD_REGEX = /%%=\ [^%%]*%%/
122
+
123
+
124
+ DTYPES_ASSIGN = {
125
+ :complex => { # Assign a complex to:
126
+ :complex => lambda {|l,r| "((#{l}*)p1)->r = ((#{r}*)p2)->r; ((#{l}*)p1)->i = ((#{r}*)p2)->i;" },
127
+ :float => lambda {|l,r| "*(#{l}*)p1 = ((#{r}*)p2)->r;" },
128
+ :int => lambda {|l,r| "*(#{l}*)p1 = ((#{r}*)p2)->r;" },
129
+ :rational => lambda {|l,r| "rb_raise(rb_eNotImpError, \"I don't know how to assign a complex to a rational\");" },
130
+ :value => lambda {|l,r| "*(VALUE*)p1 = rb_complex_new(rb_float_new(((#{r}*)p2)->r), rb_float_new(((#{r}*)p2)->i));" },
131
+ },
132
+ :float => {
133
+ :complex => lambda {|l,r| "((#{l}*)p1)->i = 0; ((#{l}*)p1)->r = *(#{r}*)p2;" },
134
+ :float => lambda {|l,r| "*(#{l}*)p1 = *(#{r}*)p2;" },
135
+ :int => lambda {|l,r| "*(#{l}*)p1 = *(#{r}*)p2;" },
136
+ :rational => lambda {|l,r| "rb_raise(rb_eNotImpError, \"I don't know how to assign a float to a rational\");" },
137
+ :value => lambda {|l,r| "*(VALUE*)p1 = rb_float_new(*(#{r}*)p2);" },
138
+ },
139
+ :int => {
140
+ :complex => lambda {|l,r| "((#{l}*)p1)->i = 0; ((#{l}*)p1)->r = *(#{r}*)p2;" },
141
+ :float => lambda {|l,r| "*(#{l}*)p1 = *(#{r}*)p2;" },
142
+ :int => lambda {|l,r| "*(#{l}*)p1 = *(#{r}*)p2;" },
143
+ :rational => lambda {|l,r| "((#{l}*)p1)->d = 1; ((#{l}*)p1)->n = *(#{r}*)p2;" },
144
+ :value => lambda {|l,r| "*(VALUE*)p1 = INT2NUM(*(#{r}*)p2);" },
145
+ },
146
+ :rational => {
147
+ :complex => lambda {|l,r| "((#{l}*)p1)->i = 0; ((#{l}*)p1)->r = ((#{r}*)p2)->n / (double)((#{r}*)p2)->d;" },
148
+ :float => lambda {|l,r| "*(#{l}*)p1 = ((#{r}*)p2)->n / (double)((#{r}*)p2)->d;" },
149
+ :int => lambda {|l,r| "*(#{l}*)p1 = ((#{r}*)p2)->n / ((#{r}*)p2)->d;" },
150
+ :rational => lambda {|l,r| "((#{l}*)p1)->d = ((#{r}*)p2)->d; ((#{l}*)p1)->n = ((#{r}*)p2)->n;" },
151
+ :value => lambda {|l,r| "*(VALUE*)p1 = rb_rational_new(INT2FIX(((#{r}*)p2)->n), INT2FIX(((#{r}*)p2)->d));" }
152
+ },
153
+ :value => {
154
+ :complex => lambda {|l,r| "((#{l}*)p1)->r = REAL2DBL(*(VALUE*)p2); ((#{l}*)p1)->i = IMAG2DBL(*(VALUE*)p2);" },
155
+ :float => lambda {|l,r| "*(#{l}*)p1 = NUM2DBL(*(VALUE*)p2);"},
156
+ :int => lambda {|l,r| "*(#{l}*)p1 = NUM2DBL(*(VALUE*)p2);"},
157
+ :rational => lambda {|l,r| "((#{l}*)p1)->n = NUMER2INT(*(VALUE*)p2); ((#{l}*)p1)->d = DENOM2INT(*(VALUE*)p2);" },
158
+ :value => lambda {|l,r| "*(VALUE*)p1 = *(VALUE*)p2;"}
159
+ }
160
+ }
161
+
162
+
163
+ class << self
164
+
165
+ def decl spec_name, ary
166
+ a = []
167
+ a << "#{spec_name} {"
168
+ ary.each do |v|
169
+ a << " #{v.to_s},"
170
+ end
171
+ a << "};"
172
+ a.join("\n") + "\n\n"
173
+ end
174
+
175
+
176
+ def dtypes_err_functions
177
+ str = <<SETFN
178
+ static void TypeErr(void) {
179
+ rb_raise(rb_eTypeError, "illegal operation with this type");
180
+ }
181
+
182
+ SETFN
183
+ end
184
+
185
+ def dtypes_function_name func, dtype_i, dtype_j = nil
186
+ if dtype_i[:enum] == :NM_NONE || (!dtype_j.nil? && dtype_j[:enum] == :NM_NONE)
187
+ str = "TypeErr"
188
+ else
189
+ str = func.to_s.camelize
190
+ str += "_#{dtype_i[:id]}"
191
+ str += "_#{dtype_j[:id]}" unless dtype_j.nil?
192
+ end
193
+ str
194
+ end
195
+
196
+ def dtypes_assign lhs, rhs
197
+ Generator::DTYPES_ASSIGN[ rhs.type ][ lhs.type ].call( lhs.sizeof, rhs.sizeof )
198
+ end
199
+
200
+
201
+
202
+ # Declare a set function for a pair of dtypes
203
+ def dtypes_set_function dtype_i, dtype_j
204
+ str = <<SETFN
205
+ static void #{dtypes_function_name(:set, dtype_i, dtype_j)}(size_t n, char* p1, size_t i1, char* p2, size_t i2) {
206
+ for (; n > 0; --n) {
207
+ #{dtypes_assign(dtype_i, dtype_j)}
208
+ p1 += i1; p2 += i2;
209
+ }
210
+ }
211
+
212
+ SETFN
213
+ end
214
+
215
+ def dtypes_increment_function dtype_i
216
+ str = <<INCFN
217
+ static void #{dtypes_function_name(:increment, dtype_i)}(void* p) { ++(*(#{dtype_i[:sizeof]}*)p); }
218
+ INCFN
219
+ end
220
+
221
+ def dtypes_upcast
222
+ ary = Array.new(15) { Array.new(15, nil) }
223
+ DTYPES.each_index do |a|
224
+ ad = DTYPES[a]
225
+ (a...DTYPES.size).each do |b|
226
+ bd = DTYPES[b]
227
+
228
+ entry = nil
229
+
230
+ if ad.type == :none || bd.type == :none
231
+ entry ||= 'NM_NONE'
232
+ elsif bd.type == ad.type
233
+ entry ||= DTYPES[[a,b].max].enum.to_s
234
+ elsif ad.type == :int # to float, complex, rational, or value
235
+ entry ||= DTYPES[[a,b].max].enum.to_s
236
+ elsif ad.enum == :NM_FLOAT32 # to complex or value
237
+ if [:NM_FLOAT64, :NM_COMPLEX64, :NM_COMPLEX128, :NM_ROBJ].include?(bd.enum)
238
+ entry ||= DTYPES[b].enum.to_s
239
+ elsif [:NM_RATIONAL32, :NM_RATIONAL64, :NM_RATIONAL128].include?(bd.enum)
240
+ entry ||= 'NM_FLOAT64'
241
+ else
242
+ entry ||= DTYPES[a].enum.to_s
243
+ end
244
+ elsif ad.enum == :NM_FLOAT64 # to complex or value
245
+ if [:NM_COMPLEX128, :NM_ROBJ].include?(bd.enum)
246
+ entry ||= DTYPES[b].enum.to_s
247
+ elsif bd.enum == :NM_COMPLEX64
248
+ entry ||= 'NM_COMPLEX128'
249
+ else
250
+ entry ||= DTYPES[a].enum.to_s
251
+ end
252
+ elsif ad.type == :rational # to float, complex, or value
253
+ if [:NM_FLOAT64, :NM_COMPLEX128, :NM_ROBJ].include?(bd.enum)
254
+ entry ||= DTYPES[b].enum.to_s
255
+ elsif bd.enum == :NM_FLOAT32
256
+ entry ||= 'NM_FLOAT64'
257
+ elsif bd.enum == :NM_COMPLEX64
258
+ entry ||= 'NM_COMPLEX128'
259
+ else
260
+ entry ||= DTYPES[a].enum.to_s
261
+ end
262
+ elsif ad.type == :complex
263
+ if bd.enum == :NM_ROBJ
264
+ entry ||= DTYPES[b].enum.to_s
265
+ else
266
+ entry ||= DTYPES[a].enum.to_s
267
+ end
268
+ elsif ad.type == :value # always value
269
+ entry ||= DTYPES[a].enum.to_s
270
+ end
271
+
272
+ ary[a][b] = ary[b][a] = entry
273
+ end
274
+ end
275
+
276
+ res = []
277
+ ary.each_index do |i|
278
+ res << "{ " + ary[i].join(", ") + " }"
279
+ end
280
+ decl("const int8_t Upcast[#{DTYPES.size}][#{DTYPES.size}] =", res) + "\n"
281
+ end
282
+
283
+ # binary-style functions, like Set (copy)
284
+ def dtypes_binary_functions_matrix func
285
+ ary = []
286
+ DTYPES.each do |i|
287
+ next if i[:enum] == :NM_TYPES
288
+ bry = []
289
+ DTYPES.each do |j|
290
+ next if j[:enum] == :NM_TYPES
291
+ bry << dtypes_function_name(func, i,j)
292
+ end
293
+ ary << "{ " + bry.join(", ") + " }"
294
+ end
295
+ ary
296
+ end
297
+
298
+
299
+ def dtypes_increment_functions_array
300
+ ary = []
301
+ DTYPES.each do |i|
302
+ next if i[:enum] == :NM_TYPES
303
+ if [:NM_INT8, :NM_INT16, :NM_INT32, :NM_INT64].include?(i.enum)
304
+ ary << dtypes_function_name(:increment, i)
305
+ else
306
+ ary << dtypes_function_name(:increment, DTYPES[0]) # TypeErr
307
+ end
308
+ end
309
+ ary
310
+ end
311
+
312
+
313
+ def dtypes_set_functions
314
+ ary = []
315
+
316
+ ary << dtypes_err_functions
317
+
318
+ DTYPES.each do |dtype_i|
319
+ DTYPES.each do |dtype_j|
320
+ begin
321
+ setfn = dtypes_set_function(dtype_i, dtype_j)
322
+ ary << setfn unless setfn =~ /TypeErr/
323
+ rescue NotImplementedError => e
324
+ STDERR.puts "Warning: #{e.to_s}"
325
+ rescue NoMethodError => e
326
+ # do nothing
327
+ end
328
+ end
329
+ end
330
+ ary << ""
331
+ ary << decl("nm_setfunc_t SetFuncs =", dtypes_binary_functions_matrix(:set))
332
+
333
+ ary.join("\n")
334
+ end
335
+
336
+ def dtypes_increment_functions
337
+ ary = []
338
+
339
+ DTYPES.each do |dtype_i|
340
+ next unless [:NM_INT8, :NM_INT16, :NM_INT32, :NM_INT64].include?(dtype_i.enum)
341
+ incfn = dtypes_increment_function(dtype_i)
342
+ ary << incfn unless incfn =~ /TypeErr/
343
+ end
344
+
345
+ ary << ""
346
+ ary << decl("nm_incfunc_t Increment =", dtypes_increment_functions_array)
347
+
348
+ ary.join("\n")
349
+ end
350
+
351
+
352
+ def dtypes_enum
353
+ decl("enum NMatrix_DTypes", DTYPES.map{ |d| d[:enum].to_s })
354
+ end
355
+
356
+ def dtypes_sizeof
357
+ decl("const int nm_sizeof[#{DTYPES.size}] =", DTYPES.map { |d| d[:sizeof].is_a?(Fixnum) ? d[:sizeof] : "sizeof(#{d[:sizeof].to_s})"})
358
+ end
359
+
360
+ def dtypes_typestring
361
+ decl("const char *nm_dtypestring[] =", DTYPES.map { |d| "\"#{d[:sym].to_s}\"" })
362
+ end
363
+
364
+
365
+ def make_file filename, &block
366
+ STDERR.puts "generated #{filename}"
367
+ f = File.new(filename, "w")
368
+ file_symbol = filename.split('.').join('_').upcase
369
+
370
+ f.puts "/* Automatically created using generator.rb - do not modify! */"
371
+
372
+ f.puts "#ifndef #{file_symbol}\n# define #{file_symbol}\n\n"
373
+ yield f
374
+ f.puts "\n#endif\n\n"
375
+ f.close
376
+ end
377
+
378
+
379
+ def make_dtypes_c
380
+ make_file "dtypes.c" do |f|
381
+ f.puts dtypes_sizeof
382
+ f.puts dtypes_typestring
383
+ f.puts dtypes_upcast
384
+ end
385
+ end
386
+
387
+
388
+ def make_dtypes_h
389
+ make_file "dtypes.h" do |f|
390
+ f.puts dtypes_enum
391
+ end
392
+ end
393
+
394
+
395
+ def make_dfuncs_c
396
+ make_file "dfuncs.c" do |f|
397
+ f.puts '#include <ruby.h>'
398
+ f.puts '#include "nmatrix.h"' + "\n\n"
399
+ f.puts dtypes_set_functions
400
+ f.puts dtypes_increment_functions
401
+ end
402
+ end
403
+
404
+
405
+ # Read templates given by +names+ from <tt>SRC_DIR/relative_path</tt>, and output them to a filename described by
406
+ # +output_name+.
407
+ #
408
+ # == Example
409
+ #
410
+ # make_templated_c './smmp', 'header', %w{numbmm transp bstoy ytobs}, "smmp1.c", {:TYPE => RATIONAL_DTYPES, :INT => INDEX_DTYPES}
411
+ #
412
+ # TODO: index dtype is unsigned!
413
+ # That means instead of int8_t, we should be doing uint8_t. But can't always do that because the Fortran code
414
+ # occasionally starts at index -1. Stupid Fortran! Someone needs to go through and fix the code by hand.
415
+ #
416
+ # TODO: Write tests to confirm that the signedness isn't screwing up stuff.
417
+ #
418
+ # TODO: Make templates work with complex and rational types too.
419
+ #
420
+ def make_templated_c relative_path, header_name, names, output_name, subs = {:TYPE => INDEX_DTYPES}
421
+
422
+ # First print the header once
423
+ `cat #{$RELATIVE_PATH}/#{SRC_DIR}/#{relative_path}/#{header_name}.template.c > ./#{output_name}` unless header_name.nil?
424
+
425
+ subs[:TYPE].each do |type|
426
+ if subs.has_key?(:INT)
427
+ subs[:INT].each do |int|
428
+ names.each do |name|
429
+ template "#{$RELATIVE_PATH}/#{SRC_DIR}/#{relative_path}/#{name}.template.c", output_name, :TYPE => type, :INT => int
430
+ end
431
+ end
432
+ else
433
+ names.each do |name|
434
+ template "#{$RELATIVE_PATH}/#{SRC_DIR}/#{relative_path}/#{name}.template.c", output_name, :TYPE => type
435
+ end
436
+ end
437
+ end
438
+ end
439
+
440
+ def sub_int_real relative_path, name, output_name, int_dtype, real_dtype
441
+ cmd = ["#{$RELATIVE_PATH}/#{SRC_DIR}/#{relative_path}/#{name}.template.c",
442
+ "sed s/%%INT_ABBREV%%/#{int_dtype.id}/g",
443
+ "sed s/%%INT%%/#{int_dtype.sizeof}/g",
444
+ "sed s/%%INT_MAX%%/#{int_dtype.max_macro}/g",
445
+ "sed s/%%REAL_ABBREV%%/#{real_dtype.id}/g",
446
+ "sed s/%%REAL%%/#{real_dtype.sizeof}/g" ]
447
+
448
+ raise(ArgumentError, "no such template '#{name}'; looked at #{cmd[0]}") unless File.exist?(cmd[0])
449
+
450
+ `cat #{cmd.join(' | ')} >> ./#{output_name}`
451
+ end
452
+
453
+
454
+ def sub_int relative_path, name, output_name, dtype
455
+ cmd = ["#{$RELATIVE_PATH}/#{SRC_DIR}/#{relative_path}/#{name}.template.c",
456
+ "sed s/%%INT_ABBREV%%/#{dtype.id}/g",
457
+ "sed s/%%INT%%/#{dtype.sizeof}/g",
458
+ "sed s/%%INT_MAX%%/#{dtype.max_macro}/g"]
459
+
460
+ raise(ArgumentError, "no such template '#{name}'; looked at #{cmd[0]}") unless File.exist?(cmd[0])
461
+
462
+ `cat #{cmd.join(' | ')} >> ./#{output_name}`
463
+ end
464
+
465
+
466
+ # Evaluate one-line Ruby statements embedded in a template.
467
+ def gsub_yield line, t, dtype, line_number=nil, filename=nil
468
+ match = line.match YIELD_REGEX
469
+ while !match.nil?
470
+
471
+ statement = match[0][4...-2]
472
+ result = self.send :eval, statement, binding, filename, line_number
473
+ line["%%= #{statement}%%"] = result.to_s
474
+
475
+ match = line.match YIELD_REGEX
476
+ end
477
+ line
478
+ end
479
+
480
+
481
+ def gsub_expression_re re, line, t, dtype, line_number=nil, filename=nil
482
+ match = line.match re
483
+ while !match.nil?
484
+ expression = match[0][t.size+3...-2]
485
+ operation = SyntaxTree.parse(expression)
486
+
487
+ begin
488
+ operation_output = operation.operate(dtype.type, dtype.id)
489
+
490
+ # Correctly join together the lines of output operations and insert them into the template line
491
+ if operation.is_boolean?
492
+ line["%%#{t} #{expression}%%"] = operation_output[0]
493
+ else
494
+ line["%%#{t} #{expression}%%"] = operation_output.join(";\n") + ";"
495
+ end
496
+
497
+ rescue NotImplementedError => e
498
+ STDERR.puts "Error: #{e.inspect}"
499
+ raise(SyntaxError, "possible NotImplementedError (#{dtype.type}) in template #{filename}: #{line_number}: \"#{expression}\"")
500
+ rescue IndexError
501
+ raise(StandardError, "string not matched: '%%#{t} #{expression}%%'")
502
+ end
503
+
504
+ match = line.match re
505
+ end
506
+ line
507
+ end
508
+
509
+
510
+ # Replace a pseudo-mathematical expression with an actual one with dtypes taken into account.
511
+ def gsub_expression line, t, dtype, line_number=nil, filename=nil
512
+ gsub_expression_re /%%#{t}\ .*?%%/, line, t, dtype, line_number, filename
513
+ end
514
+
515
+ def gsub_expression_long line, t, dtype, line_number=nil, filename=nil
516
+ gsub_expression_re /%%#{t}_LONG\ .*?%%/, line, "#{t}_LONG", dtype.long_dtype, line_number, filename
517
+ end
518
+
519
+
520
+ # Replaces sub_int_real and sub_int.
521
+ #
522
+ # Allows more flexible substitutions. Pass a hash of templates, e.g., {:INT => INDEX_DTYPES[0], :REAL => RATIONAL_DTYPES[1]}, and
523
+ # it'll produce all possible combinations thereof.
524
+ #
525
+ # At some point we should probably just switch to erb. This just started growing and pretty soon I realized
526
+ # erb would likely have been a better option. Oh well.
527
+ def template template_filepath, output_filepath, types = {}
528
+ raise(ArgumentError, "expected substitution templates") if types.size == 0
529
+
530
+ #STDERR.puts "output_filepath = #{output_filepath}; Dir.pwd = #{Dir.pwd}"
531
+
532
+ output = File.new output_filepath, "a" # append
533
+ template = File.new template_filepath, "r"
534
+
535
+ line_count = 1
536
+
537
+ while line = template.gets
538
+ line.chomp!
539
+
540
+ types.each_pair do |t_sym,dtype|
541
+ t = t_sym.to_s
542
+
543
+ #STDERR.puts "Processing #{template_filepath}: #{line}"
544
+ if line.include?("%%#{t}")
545
+ line.gsub! "%%#{t}%%", dtype.sizeof.to_s
546
+ line.gsub! "%%#{t}_ABBREV%%", dtype.id.to_s
547
+ line.gsub! "%%#{t}_MAX%%", dtype.max_macro
548
+ line.gsub! "%%#{t}_LONG%%", dtype.long_dtype.sizeof.to_s #e.g., int64 instead of int8 for temporary variables
549
+
550
+ # Get any mathematical expressions that need to be translated
551
+ line = gsub_expression(line, t, dtype, line_count, template_filepath)
552
+
553
+ # Do the same for temp variables (which are often going to be more bytes)
554
+ line = gsub_expression_long(line, t, dtype, line_count, template_filepath)
555
+ end
556
+
557
+ # Deal with any Ruby statements in the template.
558
+ if line.include?("%%=")
559
+ line = gsub_yield(line, t, dtype, line_count, template_filepath)
560
+ end
561
+
562
+ end
563
+
564
+ line_count += 1
565
+
566
+ output.puts line
567
+ end
568
+
569
+ output.close
570
+
571
+ output_filepath
572
+ end
573
+
574
+
575
+ end
576
+ end
577
+
578
+ if $IN_MAKEFILE
579
+ Generator.make_dtypes_h
580
+ Generator.make_dtypes_c
581
+ Generator.make_dfuncs_c
582
+ Generator.make_templated_c './yale', 'smmp1_header', %w{smmp1}, 'smmp1.c', :TYPE => Generator::INDEX_DTYPES # 1-type interface functions for SMMP
583
+ Generator.make_templated_c './yale', nil, %w{smmp2}, 'smmp1.c', :TYPE => Generator::ACTUAL_DTYPES, :INT => Generator::INDEX_DTYPES # 2-type interface functions for SMMP
584
+ Generator.make_templated_c './yale', 'smmp2_header', %w{symbmm}, 'smmp2.c', :TYPE => Generator::INDEX_DTYPES # 1-type SMMP functions from Fortran
585
+ Generator.make_templated_c './yale', nil, %w{complexmath}, 'smmp2.c', :TYPE => Generator::COMPLEX_DTYPES
586
+ Generator.make_templated_c './yale', nil, %w{elementwise_op}, 'smmp2.c', :TYPE => Generator::ACTUAL_DTYPES
587
+ Generator.make_templated_c './yale', nil, %w{numbmm transp sort_columns elementwise}, 'smmp2.c', :TYPE => Generator::ACTUAL_DTYPES, :INT => Generator::INDEX_DTYPES # 2-type SMMP functions from Fortran and selection sort
588
+ Generator.make_templated_c './dense', 'blas_header', %w{rationalmath}, 'blas.partial.c', :TYPE => Generator::RATIONAL_DTYPES
589
+ Generator.make_templated_c './yale', nil, %w{complexmath}, 'blas.partial.c', :TYPE => Generator::COMPLEX_DTYPES
590
+ Generator.make_templated_c './dense', nil, %w{gemm gemv}, 'blas.partial.c', :TYPE => Generator::NONBLAS_DTYPES
591
+ Generator.make_templated_c './dense', nil, %w{elementwise}, 'blas.partial.c', :TYPE=>Generator::ACTUAL_DTYPES
592
+ `cat blas.partial.c >> blas.c`
593
+ `rm blas.partial.c`
594
+ end