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,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