multiarray 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/COPYING CHANGED
@@ -1,5 +1,5 @@
1
1
  This is the license for the computer vision library Hornetseye.
2
- Copyright (C) 2006, 2007, 2008, 2009 Jan Wedekind, Sheffield, United Kingdom.
2
+ Copyright (C) 2006 - 2010 Jan Wedekind, Sheffield, United Kingdom.
3
3
 
4
4
 
5
5
  GNU GENERAL PUBLIC LICENSE
data/README.md ADDED
@@ -0,0 +1,3 @@
1
+ MultiArray
2
+ ==========
3
+ This Ruby-extension defines the class {Hornetseye::MultiArray} and other native datatypes. {Hornetseye::MultiArray} provides multi-dimensional Ruby arrays with elements of same type. The extension is designed to be mostly compatible with Masahiro Tanaka's NArray. However it allows the definition of custom element types and operations on them. This work was also inspired by Ronald Garcia's boost::multi_array and by Todd Veldhuizen's Blitz++.
data/Rakefile CHANGED
@@ -6,11 +6,11 @@ require 'rake/packagetask'
6
6
  require 'rbconfig'
7
7
 
8
8
  PKG_NAME = 'multiarray'
9
- PKG_VERSION = '0.4.1'
9
+ PKG_VERSION = '0.5.0'
10
10
  RB_FILES = FileList[ 'lib/**/*.rb' ]
11
11
  TC_FILES = FileList[ 'test/tc_*.rb' ]
12
12
  TS_FILES = FileList[ 'test/ts_*.rb' ]
13
- PKG_FILES = [ 'Rakefile', 'README', 'COPYING', 'TODO', '.document' ] +
13
+ PKG_FILES = [ 'Rakefile', 'README.md', 'COPYING', 'TODO', '.document' ] +
14
14
  RB_FILES + TS_FILES + TC_FILES
15
15
  SUMMARY = %q{Multi-dimensional and uniform Ruby arrays}
16
16
  DESCRIPTION = %q{This Ruby-extension defines Hornetseye::MultiArray and other native datatypes. Hornetseye::MultiArray provides multi-dimensional Ruby arrays with elements of same type. The extension is designed to be mostly compatible with Masahiro Tanaka's NArray. However it allows the definition of custom element types and operations on them. This work was also inspired by Ronald Garcia's boost::multi_array and by Todd Veldhuizen's Blitz++.}
@@ -30,8 +30,8 @@ desc 'Install Ruby extension'
30
30
  task :install do
31
31
  verbose true do
32
32
  for f in RB_FILES do
33
- FileUtils.mkdir_p "#{$SITELIBDIR}/#{File.dirname( f[ 4 .. -1 ] )}"
34
- FileUtils.cp_r f, "#{$SITELIBDIR}/#{f[ 4 .. -1 ]}"
33
+ FileUtils.mkdir_p "#{$SITELIBDIR}/#{File.dirname f.gsub( /^lib\//, '' )}"
34
+ FileUtils.cp_r f, "#{$SITELIBDIR}/#{f.gsub /^lib\//, ''}"
35
35
  end
36
36
  end
37
37
  end
@@ -40,7 +40,7 @@ desc 'Uninstall Ruby extension'
40
40
  task :uninstall do
41
41
  verbose true do
42
42
  for f in RB_FILES do
43
- FileUtils.rm_f "#{$SITELIBDIR}/#{f[ 4 .. -1 ]}"
43
+ FileUtils.rm_f "#{$SITELIBDIR}/#{f.gsub /^lib\//, ''}"
44
44
  end
45
45
  end
46
46
  end
@@ -84,7 +84,8 @@ begin
84
84
  s.has_rdoc = 'yard'
85
85
  s.extra_rdoc_files = []
86
86
  s.rdoc_options = %w{--no-private}
87
- s.add_dependency %q<malloc>, [ '~> 0.2' ]
87
+ s.add_dependency %q<malloc>, [ '~> 1.0' ]
88
+ s.add_development_dependency %q{rake}
88
89
  end
89
90
  GEM_SOURCE = "#{PKG_NAME}-#{PKG_VERSION}.gem"
90
91
  desc "Build the gem file #{GEM_SOURCE}"
data/TODO CHANGED
@@ -1,7 +1,82 @@
1
- ranges (rolling, lazy rolling, lazy ranges)
1
+ # test for multiarray-indgen
2
+ # accept ranges for element access
3
+ # compile Convolve#demand
4
+ # inject without initial is [ 1 .. -1 ].inject with [ 0 ] as initial value
5
+ # Composite numbers?
6
+ # pointer-increments for better efficiency
7
+ # How does contiguous work here? typecasts?
8
+ # separate secondary operations like equality
9
+ # preload cache
10
+ # Sequence( UBYTE, 3 )[ ... ]
11
+
12
+ # histogram (implement using injection)
13
+ # inject: min, max, equal, n-d clips for warp
14
+ # block(var1,var2,...) with smart subst?
15
+ # lazy( 5 ) { |i| 0 } # but lazy { 0 }
16
+ # lazy( 3, 2 ) { |i,j| i }
17
+ # lazy( 3, 2 ) { |i,j| j.to_object }
18
+
19
+ # RSpec
20
+
21
+ # downsampling after correlation?
22
+ # README.rdoc, YARD documentation with pictures, demo video
23
+
24
+ # f(g(i)) # g(i), f(g(i)) all can be vectors and all can be lazy
25
+ # lut(g(i))
26
+ # f(warp(i))
27
+ #class Node
28
+ # def map( lut, options = {} )
29
+ #
30
+ # end
31
+ #end
32
+
33
+
34
+ # cache <-> demand/get? what about store?
35
+ # Composite numbers?
36
+ # change Convolve#demand to generate GCC code
37
+ # inspect method (type method to get MultiArray( ... ) or Sequence( ... ))
38
+ # put JIT-calls into 'force'-method, don't call JIT for OBJECT
39
+ # -> multiarray gem
40
+ # pointer-increments for better efficiency
41
+ # use pid, allow creation of library for pre-loading cache
42
+
43
+ # histogram
44
+ # binary operations, equality: ( a == b ).inject( true ) { |e,b| e && b }
45
+ # inject: min, max, equal, n-d clips for warp
46
+ # block(var1,var2,...) with smart subst?
47
+ # lazy( 5 ) { |i| 0 }
48
+ # lazy( 3, 2 ) { |i,j| i }
49
+ # lazy( 3, 2 ) { |i,j| j }
50
+
51
+ # downsampling after correlation?
52
+ # ruby -Ilib -rrubygems -rmultiarray -rtest test/tc_sequence.rb
53
+ # README.rdoc, demo video
54
+
55
+ # f(g(i)) # g(i), f(g(i)) all can be vectors and all can be lazy
56
+ # lut(g(i))
57
+ # f(warp(i))
58
+ #class Node
59
+ # def map( lut, options = {} ) # !!!
60
+ #
61
+ # end
62
+ #end
63
+
64
+
65
+ lazy { a * b } -> lazy { |i,j| lazy { |i,j| a[i,j] * b[i,j] }[i,j] } # ?
66
+ lazy { s } -> s
67
+ lazy { -s } -> lazy { |i| -s[i] }
68
+ lazy { |i,j| s[i,j] }
69
+ lazy { |i| lazy { |j| s[i,j] } }
70
+ lazy( n ) { |i| i }
71
+ lazy( n, m ) { |i,j| i + j }
72
+ lazy( n ) { |i| lazy( m ) { |j| i + j } }
73
+ eager( n ) { |i| i }
74
+ ...
75
+
2
76
  tensor indices to enable transpose of lazy array
77
+ ranges (rolling, lazy rolling, lazy ranges)
3
78
  test lazyness
4
- sums/injections? nesting? tensors?
79
+ sums/injections (equality for arrays)? nesting? tensors?
5
80
  JIT?
6
81
  test type conversions
7
82
  fancy README
@@ -52,6 +127,8 @@ proc { |i| proc { |j| i+j } }.call( 5 ).call 3
52
127
 
53
128
  gem install flay: http://ruby.sadi.st/Flay.html
54
129
 
130
+ # donate to yardoc
131
+
55
132
  How to nest/cascade mode-environments?
56
133
  (how to specify nested modes for recursive algorithms and called algorithms?)
57
134
  ruby and jit compiles, lazy and parallel forwards
data/lib/multiarray.rb CHANGED
@@ -1,10 +1,49 @@
1
+ # multiarray - Lazy multi-dimensional arrays for Ruby
2
+ # Copyright (C) 2010 Jan Wedekind
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ # Module#alias_method_chain is defined.
18
+ #
19
+ # @private
20
+ class Module
21
+
22
+ unless method_defined? :alias_method_chain
23
+
24
+ # Method for creating alias chains.
25
+ #
26
+ # @private
27
+ def alias_method_chain( target, feature, vocalize = target )
28
+ alias_method "#{vocalize}_without_#{feature}", target
29
+ alias_method target, "#{vocalize}_with_#{feature}"
30
+ end
31
+
32
+ end
33
+
34
+ end
35
+
1
36
  # Proc#bind is defined if it does not exist already
37
+ #
38
+ # @private
2
39
  class Proc
3
40
 
4
41
  unless method_defined? :bind
5
42
 
6
43
  # Proc#bind is defined if it does not exist already
7
44
  #
45
+ # @param [Object] object Object to bind this instance of +Proc+ to.
46
+ #
8
47
  # @private
9
48
  def bind( object )
10
49
  block, time = self, Time.now
@@ -21,7 +60,7 @@ class Proc
21
60
 
22
61
  end
23
62
 
24
- # Object#instance_exec is defined if it does not exist already
63
+ # +Object+ is extended with a few methods
25
64
  class Object
26
65
 
27
66
  unless method_defined? :instance_exec
@@ -35,18 +74,316 @@ class Object
35
74
 
36
75
  end
37
76
 
77
+ # Boolean negation
78
+ #
79
+ # @return [FalseClass] Returns +false+.
80
+ #
81
+ # @see NilClass#not
82
+ # @see FalseClass#not
83
+ def not
84
+ false
85
+ end
86
+
87
+ # Boolean 'and' operation
88
+ #
89
+ # @param [FalseClass,TrueClass,Object] other Other boolean object.
90
+ # @return [FalseClass,TrueClass] Returns +other+.
91
+ #
92
+ # @see FalseClass#and
93
+ # @see NilClass#and
94
+ def and( other )
95
+ unless other.is_a? Hornetseye::Node
96
+ other
97
+ else
98
+ x, y = other.coerce self
99
+ x.and y
100
+ end
101
+ end
102
+
103
+ # Boolean 'or' operation
104
+ #
105
+ # @param [FalseClass,TrueClass,Object] other Other boolean object.
106
+ # @return [TrueClass] Returns +true+.
107
+ #
108
+ # @see FalseClass#or
109
+ # @see NilClass#or
110
+ def or( other )
111
+ unless other.is_a? Hornetseye::Node
112
+ self
113
+ else
114
+ x, y = other.coerce self
115
+ x.or y
116
+ end
117
+ end
118
+
119
+ # Element-wise equal operator
120
+ #
121
+ # The method calls +self == other+ unless +other+ is of type
122
+ # Hornetseye::Node. In that case an element-wise comparison using
123
+ # Hornetseye::Node#eq is performed after coercion.
124
+ #
125
+ # @return [FalseClass,TrueClass,Hornetseye::Node] Result of comparison.
126
+ # @see Hornetseye::Node
127
+ # @see Hornetseye::Binary_
128
+ def eq( other )
129
+ unless other.is_a? Hornetseye::Node
130
+ self == other
131
+ else
132
+ x, y = other.coerce self
133
+ x.eq y
134
+ end
135
+ end
136
+
137
+ # Element-wise not-equal operator
138
+ #
139
+ # The method calls +( self == other ).not+ unless +other+ is of type
140
+ # Hornetseye::Node. In that case an element-wise comparison using
141
+ # Hornetseye::Node#ne is performed after coercion.
142
+ #
143
+ # @return [FalseClass,TrueClass,Hornetseye::Node] Result of comparison.
144
+ # @see Hornetseye::Node
145
+ # @see Hornetseye::Binary_
146
+ def ne( other )
147
+ unless other.is_a? Hornetseye::Node
148
+ ( self == other ).not
149
+ else
150
+ x, y = other.coerce self
151
+ x.ne y
152
+ end
153
+ end
154
+
155
+ # Boolean select operation
156
+ #
157
+ # @param [Object] a Object to select if +self+ is neither +false+ nor +nil+.
158
+ # @param [Object] b Object to select if +self+ is +false+ or +nil+.
159
+ # @return [Object] Returns +a+.
160
+ #
161
+ # @see FalseClass#conditional
162
+ # @see NilClass#conditional
163
+ def conditional( a, b )
164
+ a
165
+ end
166
+
38
167
  end
39
168
 
40
- # Range#min and Range#max are replaced for performance reasons
41
- class Range
169
+ # +NilClass+ is extended with a few methods
170
+ class NilClass
171
+
172
+ # Boolean negation
173
+ #
174
+ # @return [FalseClass] Returns +false+.
175
+ #
176
+ # @see Object#not
177
+ # @see FalseClass#not
178
+ def not
179
+ true
180
+ end
181
+
182
+ # Boolean 'and' operation
183
+ #
184
+ # @param [FalseClass,TrueClass,Object] other Other boolean object.
185
+ # @return [FalseClass] Returns +false+.
186
+ #
187
+ # @see Object#and
188
+ # @see FalseClass#and
189
+ def and( other )
190
+ unless other.is_a? Hornetseye::Node
191
+ self
192
+ else
193
+ x, y = other.coerce self
194
+ x.and y
195
+ end
196
+ end
197
+
198
+ # Boolean 'or' operation
199
+ #
200
+ # @param [FalseClass,TrueClass,Object] other Other boolean object.
201
+ # @return [FalseClass,TrueClass] Returns +other+.
202
+ #
203
+ # @see Object#or
204
+ # @see FalseClass#or
205
+ def or( other )
206
+ unless other.is_a? Hornetseye::Node
207
+ other
208
+ else
209
+ x, y = other.coerce self
210
+ x.or y
211
+ end
212
+ end
213
+
214
+ # Boolean select operation
215
+ #
216
+ # @param [Object] a Object to select if +self+ is neither +false+ nor +nil+.
217
+ # @param [Object] b Object to select if +self+ is +false+ or +nil+.
218
+ # @return [Object] Returns +b+.
219
+ #
220
+ # @see Object#conditional
221
+ # @see FalseClass#conditional
222
+ def conditional( a, b )
223
+ b
224
+ end
225
+
226
+ # Check whether this term is compilable
227
+ #
228
+ # @return [FalseClass,TrueClass] Returns +false+
229
+ #
230
+ # @private
231
+ def compilable?
232
+ false
233
+ end
234
+
235
+ end
236
+
237
+ # +FalseClass+ is extended with a few methods
238
+ #
239
+ # @see TrueClass
240
+ class FalseClass
241
+
242
+ # Boolean negation
243
+ #
244
+ # @return [FalseClass] Returns +true+.
245
+ #
246
+ # @see Object#not
247
+ # @see NilClass#not
248
+ def not
249
+ true
250
+ end
251
+
252
+ # Boolean 'and' operation
253
+ #
254
+ # @param [FalseClass,TrueClass,Object] other Other boolean object.
255
+ # @return [FalseClass] Returns +false+.
256
+ #
257
+ # @see Object#and
258
+ # @see NilClass#and
259
+ def and( other )
260
+ unless other.is_a? Hornetseye::Node
261
+ self
262
+ else
263
+ x, y = other.coerce self
264
+ x.and y
265
+ end
266
+ end
267
+
268
+ # Boolean 'or' operation
269
+ #
270
+ # @param [FalseClass,TrueClass,Object] other Other boolean object.
271
+ # @return [FalseClass,TrueClass] Returns +other+.
272
+ #
273
+ # @see Object#or
274
+ # @see NilClass#or
275
+ def or( other )
276
+ unless other.is_a? Hornetseye::Node
277
+ other
278
+ else
279
+ x, y = other.coerce self
280
+ x.or y
281
+ end
282
+ end
283
+
284
+ # Boolean select operation
285
+ #
286
+ # @param [Object] a Object to select if +self+ is neither +false+ nor +nil+.
287
+ # @param [Object] b Object to select if +self+ is +false+ or +nil+.
288
+ # @return [Object] Returns +b+.
289
+ #
290
+ # @see Object#conditional
291
+ # @see NilClass#conditional
292
+ def conditional( a, b )
293
+ b
294
+ end
295
+
296
+ end
42
297
 
43
- public
298
+ # Some methods of +Fixnum+ are modified
299
+ #
300
+ # @private
301
+ class Fixnum
302
+
303
+ # +&+ is modified to work with this library
304
+ #
305
+ # @private
306
+ def intand_with_hornetseye( other )
307
+ if other.is_a? Integer
308
+ intand_without_hornetseye other
309
+ else
310
+ x, y = other.coerce self
311
+ x & y
312
+ end
313
+ end
314
+
315
+ alias_method_chain :&, :hornetseye, :intand
316
+
317
+ # +|+ is modified to work with this library
318
+ #
319
+ # @private
320
+ def intor_with_hornetseye( other )
321
+ if other.is_a? Integer
322
+ intor_without_hornetseye other
323
+ else
324
+ x, y = other.coerce self
325
+ x | y
326
+ end
327
+ end
328
+
329
+ alias_method_chain :|, :hornetseye, :intor
330
+
331
+ # +^+ is modified to work with this library
332
+ #
333
+ # @private
334
+ def intxor_with_hornetseye( other )
335
+ if other.is_a? Integer
336
+ intxor_without_hornetseye other
337
+ else
338
+ x, y = other.coerce self
339
+ x ^ y
340
+ end
341
+ end
342
+
343
+ alias_method_chain :^, :hornetseye, :intxor
344
+
345
+ # +<<+ is modified to work with this library
346
+ #
347
+ # @private
348
+ def shl_with_hornetseye( other )
349
+ if other.is_a? Integer
350
+ shl_without_hornetseye other
351
+ else
352
+ x, y = other.coerce self
353
+ x << y
354
+ end
355
+ end
356
+
357
+ alias_method_chain :<<, :hornetseye, :shl
358
+
359
+ # +>>+ is modified to work with this library
360
+ #
361
+ # @private
362
+ def shr_with_hornetseye( other )
363
+ if other.is_a? Integer
364
+ shr_without_hornetseye other
365
+ else
366
+ x, y = other.coerce self
367
+ x >> y
368
+ end
369
+ end
370
+
371
+ alias_method_chain :>>, :hornetseye, :shr
372
+
373
+ end
374
+
375
+ # +Range+ is extended with a few methods
376
+ class Range
44
377
 
45
378
  alias_method :orig_min, :min
46
379
 
47
380
  alias_method :orig_max, :max
48
381
 
49
382
  # For performance reasons a specialised method for integers is added
383
+ #
384
+ # @return [Object] Minimum value of range.
385
+ #
386
+ # @private
50
387
  def min
51
388
  if self.begin.is_a? Integer
52
389
  self.begin
@@ -55,7 +392,11 @@ class Range
55
392
  end
56
393
  end
57
394
 
58
- # For performance reasons a specialised method for integers is added.
395
+ # For performance reasons a specialised method for integers is added
396
+ #
397
+ # @return [Object] Maximum value of range.
398
+ #
399
+ # @private
59
400
  def max
60
401
  if self.end.is_a? Integer
61
402
  exclude_end? ? self.end - 1 : self.end
@@ -64,42 +405,122 @@ class Range
64
405
  end
65
406
  end
66
407
 
67
- # Compute the size of a range.
408
+ # Compute the size of a range
409
+ #
410
+ # @return [Integer] Number of discrete values within range.
68
411
  def size
69
412
  max + 1 - min
70
413
  end
71
414
 
72
415
  end
73
416
 
74
- # Module#alias_method_chain is defined.
75
- #
76
- # @private
77
- class Module
78
-
79
- unless method_defined? :alias_method_chain
80
-
81
- # Method for creating alias chains.
82
- #
83
- # @private
84
- def alias_method_chain( target, feature, vocalize = target )
85
- alias_method "#{vocalize}_without_#{feature}", target
86
- alias_method target, "#{vocalize}_with_#{feature}"
87
- end
88
-
89
- end
90
-
91
- end
92
-
93
417
  require 'malloc'
94
- require 'multiarray/lazy'
95
- require 'multiarray/list'
418
+ require 'rbconfig'
419
+ require 'set'
420
+ require 'tmpdir'
96
421
  require 'multiarray/malloc'
97
- require 'multiarray/type'
422
+ require 'multiarray/list'
423
+ require 'multiarray/node'
424
+ require 'multiarray/element'
98
425
  require 'multiarray/object'
99
- require 'multiarray/int_'
426
+ require 'multiarray/index'
427
+ require 'multiarray/bool'
100
428
  require 'multiarray/int'
101
- require 'multiarray/pointer_'
102
429
  require 'multiarray/pointer'
103
- require 'multiarray/sequence_'
430
+ require 'multiarray/variable'
431
+ require 'multiarray/lambda'
432
+ require 'multiarray/lookup'
433
+ require 'multiarray/unary'
434
+ require 'multiarray/binary'
435
+ require 'multiarray/operations'
436
+ require 'multiarray/inject'
437
+ require 'multiarray/diagonal'
104
438
  require 'multiarray/sequence'
105
439
  require 'multiarray/multiarray'
440
+ require 'multiarray/gcctype'
441
+ require 'multiarray/gccvalue'
442
+ require 'multiarray/gcccontext'
443
+ require 'multiarray/gcccache'
444
+ require 'multiarray/gccfunction'
445
+
446
+ # Namespace of Hornetseye computer vision library
447
+ module Hornetseye
448
+
449
+ # Method for performing computations in lazy mode
450
+ #
451
+ # @param [Array<Integer>] *shape Optional shape of result. The method
452
+ # attempts to infer the shape if not specified.
453
+ # @yield Operation to compute array elements lazily.
454
+ #
455
+ # @return [Object,Node] Lazy term to compute later.
456
+ def lazy( *shape, &action )
457
+ previous = Thread.current[ :lazy ]
458
+ Thread.current[ :lazy ] = true
459
+ begin
460
+ options = shape.last.is_a?( Hash ) ? shape.pop : {}
461
+ arity = options[ :arity ] || action.arity
462
+ if arity <= 0
463
+ action.call
464
+ else
465
+ index = Variable.new shape.empty? ? Hornetseye::INDEX( nil ) :
466
+ Hornetseye::INDEX( shape.pop )
467
+ term = lazy *( shape + [ :arity => arity - 1 ] ) do |*args|
468
+ action.call *( args + [ index ] )
469
+ end
470
+ term = Node.match( term ).new term unless term.is_a? Node
471
+ Lambda.new index, term
472
+ end
473
+ ensure
474
+ Thread.current[ :lazy ] = previous
475
+ end
476
+ end
477
+
478
+ module_function :lazy
479
+
480
+ # Method for performing computations in eager mode
481
+ #
482
+ # @param [Array<Integer>] *shape Optional shape of result. The method
483
+ # attempts to infer the shape if not specified.
484
+ # @yield Operation computing array elements.
485
+ #
486
+ # @return [Object,Node] Result of computation.
487
+ def eager( *shape, &action )
488
+ previous = Thread.current[ :lazy ]
489
+ Thread.current[ :lazy ] = false
490
+ begin
491
+ retval = lazy *shape, &action
492
+ retval.is_a?( Node ) ? retval.force : retval
493
+ ensure
494
+ Thread.current[ :lazy ] = previous
495
+ end
496
+ end
497
+
498
+ module_function :eager
499
+
500
+ # Method for summing values
501
+ #
502
+ # @param [Array<Integer>] *shape Optional shape of result. The method
503
+ # attempts to infer the shape if not specified.
504
+ # @yield Operation returning array elements.
505
+ #
506
+ # @return [Object,Node] Sum of values.
507
+ def sum( *shape, &action )
508
+ options = shape.last.is_a?( Hash ) ? shape.pop : {}
509
+ arity = options[ :arity ] || action.arity
510
+ if arity <= 0
511
+ action.call
512
+ else
513
+ index = Variable.new shape.empty? ? Hornetseye::INDEX( nil ) :
514
+ Hornetseye::INDEX( shape.pop )
515
+ term = sum *( shape + [ :arity => arity - 1 ] ) do |*args|
516
+ action.call *( args + [ index ] )
517
+ end
518
+ var1 = Variable.new term.typecode
519
+ var2 = Variable.new term.typecode
520
+ Inject.new( term, index, nil, var1 + var2, var1, var2 ).force
521
+ end
522
+ end
523
+
524
+ module_function :sum
525
+
526
+ end