Module: Hornetseye::Operations

Included in:
Node
Defined in:
lib/multiarray/complex.rb,
lib/multiarray/operations.rb,
lib/multiarray/rgb.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.define_binary_op(op, coercion = :coercion) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/multiarray/operations.rb', line 37

def define_binary_op( op, coercion = :coercion )
  define_method( op ) do |other|
    unless other.is_a? Node
      other = Node.match( other, typecode ).new other
    end
    if dimension == 0 and variables.empty? and
        other.dimension == 0 and other.variables.empty?
      target = array_type.send coercion, other.array_type
      target.new simplify.get.send( op, other.simplify.get )
    else
      Hornetseye::ElementWise( lambda { |x,y| x.send op, y }, op,
                               lambda { |t,u| t.send coercion, u } ).
        new( self, other ).force
    end
  end
end

.define_unary_op(op, conversion = :contiguous) ⇒ Object



22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/multiarray/operations.rb', line 22

def define_unary_op( op, conversion = :contiguous )
  define_method( op ) do
    if dimension == 0 and variables.empty?
      target = typecode.send conversion
      target.new simplify.get.send( op )
    else
      Hornetseye::ElementWise( lambda { |x| x.send op }, op,
                               lambda { |t| t.send conversion } ).
        new( self ).force
    end
  end
end

Instance Method Details

#+@Object



90
91
92
# File 'lib/multiarray/operations.rb', line 90

def +@
  self
end

#<=>(other) ⇒ Object



140
141
142
143
144
# File 'lib/multiarray/operations.rb', line 140

def <=>( other )
  Hornetseye::lazy do
    ( self < other ).conditional -1, ( self > other ).conditional( 1, 0 )
  end
end

#b=(value) ⇒ Object



518
519
520
521
522
523
524
525
526
527
528
# File 'lib/multiarray/rgb.rb', line 518

def b=( value )
  if typecode < RGB_
    decompose( 2 )[] = value
  elsif typecode == OBJECT
    self[] = Hornetseye::lazy do
      r * RGB.new( 1, 0, 0 ) + g * RGB.new( 0, 1, 0 ) + value * RGB.new( 0, 0, 1 )
    end
  else
    raise "Cannot assign blue channel to object of type #{array_type.inspect}"
  end
end

#b_with_decomposeObject



506
507
508
509
510
511
512
513
514
# File 'lib/multiarray/rgb.rb', line 506

def b_with_decompose
  if typecode == OBJECT or is_a?( Variable )
    b_without_decompose
  elsif typecode < RGB_
    decompose 2
  else
    self
  end
end

#collect(&action) ⇒ Object Also known as: map



185
186
187
188
189
190
# File 'lib/multiarray/operations.rb', line 185

def collect( &action )
  var = Variable.new typecode
  block = action.call var
  conversion = lambda { |t| t.to_type action.call( Variable.new( t.typecode ) ) }
  Hornetseye::ElementWise( action, block.to_s, conversion ).new( self ).force
end

#conditional(a, b) ⇒ Object



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/multiarray/operations.rb', line 121

def conditional( a, b )
  unless a.is_a? Node
    a = Node.match( a, b.is_a?( Node ) ? b : nil ).new a
  end
  unless b.is_a? Node
    b = Node.match( b, a.is_a?( Node ) ? a : nil ).new b
  end
  if dimension == 0 and variables.empty? and
    a.dimension == 0 and a.variables.empty? and
    b.dimension == 0 and b.variables.empty?
    target = array_type.cond a.array_type, b.array_type
    target.new simplify.get.conditional( a.simplify.get, b.simplify.get )
  else
    Hornetseye::ElementWise( lambda { |x,y,z| x.conditional y, z }, :conditional,
                             lambda { |t,u,v| t.cond u, v } ).
      new( self, a, b ).force
  end
end

#convolve(filter) ⇒ Node

Convolution with other array of same dimension

Parameters:

  • filter (Node)

    Filter to convolve with.

Returns:

  • (Node)

    Result of convolution.



353
354
355
356
# File 'lib/multiarray/operations.rb', line 353

def convolve( filter )
  filter = Node.match( filter, typecode ).new filter unless filter.is_a? Node
  product( filter ).diagonal { |s,x| s + x }
end

#diagonal(initial = nil, options = {}) { ... } ⇒ Node

Apply accumulative operation over elements diagonally

This method is used internally to implement convolutions.

Parameters:

  • initial (Object, Node) (defaults to: nil)

    Initial value.

  • options (Hash) (defaults to: {})

    a customizable set of options

Options Hash (options):

  • :var1 (Variable)

    First variable defining operation.

  • :var2 (Variable)

    Second variable defining operation.

  • :block (Variable) — default: yield( var1, var2 )

    The operation to apply diagonally.

Yields:

  • Optional operation to apply diagonally.

Returns:

  • (Node)

    Result of operation.

See Also:



297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
# File 'lib/multiarray/operations.rb', line 297

def diagonal( initial = nil, options = {} )
  if dimension == 0
    demand
  else
    if initial
      unless initial.is_a? Node
        initial = Node.match( initial ).new initial
      end
      initial_typecode = initial.typecode
    else
      initial_typecode = typecode
    end
    index0 = Variable.new Hornetseye::INDEX( nil )
    index1 = Variable.new Hornetseye::INDEX( nil )
    index2 = Variable.new Hornetseye::INDEX( nil )
    var1 = options[ :var1 ] || Variable.new( initial_typecode )
    var2 = options[ :var2 ] || Variable.new( typecode )
    block = options[ :block ] || yield( var1, var2 )
    value = element( index1 ).element( index2 ).
      diagonal initial, :block => block, :var1 => var1, :var2 => var2
    term = Diagonal.new( value, index0, index1, index2, initial,
                         block, var1, var2 )
    index0.size[] ||= index1.size[]
    Lambda.new( index0, term ).force
  end
end

#eq_with_multiarray(other) ⇒ Boolean

Equality operator

Returns:

  • (Boolean)

    Returns result of comparison.



221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/multiarray/operations.rb', line 221

def eq_with_multiarray( other )
  if other.is_a? Node
    if variables.empty?
      if other.array_type == array_type
        Hornetseye::eager { eq( other ).inject( true ) { |a,b| a.and b } }
      else
        false
      end
    else
      eq_without_multiarray other
    end
  else
    false
  end
end

#fill!(value = typecode.default) ⇒ Object



276
277
278
279
# File 'lib/multiarray/operations.rb', line 276

def fill!( value = typecode.default )
  self[] = value
  self
end

#g=(value) ⇒ Object



494
495
496
497
498
499
500
501
502
503
504
# File 'lib/multiarray/rgb.rb', line 494

def g=( value )
  if typecode < RGB_
    decompose( 1 )[] = value
  elsif typecode == OBJECT
    self[] = Hornetseye::lazy do
      r * RGB.new( 1, 0, 0 ) + value * RGB.new( 0, 1, 0 ) + b * RGB.new( 0, 0, 1 )
    end
  else
    raise "Cannot assign green channel to object of type #{array_type.inspect}"
  end
end

#g_with_decomposeObject



482
483
484
485
486
487
488
489
490
# File 'lib/multiarray/rgb.rb', line 482

def g_with_decompose
  if typecode == OBJECT or is_a?( Variable )
    g_without_decompose
  elsif typecode < RGB_
    decompose 1
  else
    self
  end
end

#histogram(*ret_shape) ⇒ Object



358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
# File 'lib/multiarray/operations.rb', line 358

def histogram( *ret_shape )
  options = ret_shape.last.is_a?( Hash ) ? ret_shape.pop : {}
  options = { :target => UINT, :safe => true }.merge options
  if options[ :safe ]
    if shape.first != 1 and ret_shape.size == 1
      right = Hornetseye::lazy( 1 ) { |i| self }.unroll
    else
      if shape.first != ret_shape.size
        raise "First dimension of array (#{shape.first}) differs from number of " +
              "dimensions of histogram (#{ret_shape.size})"
      end
      right = self
    end
  else
    right = self
  end
  if options[ :safe ]
    for i in 0 ... right.shape.first
      range = right.roll[ i ].range
      if range.begin < 0
        raise "#{i+1}th dimension of index must be in 0 ... #{ret_shape[i]} " +
              "(but was #{range.begin})"
      end
      if range.end >= ret_shape[ i ]
        raise "#{i+1}th dimension of index must be in 0 ... #{ret_shape[i]} " +
              "(but was #{range.end})"
      end
    end
  end
  left = MultiArray.new options[ :target ], *ret_shape
  left[] = 0
  block = Histogram.new left, right
  if block.compilable?
    GCCFunction.run block
  else
    block.demand
  end
  left
end

#imag=(value) ⇒ Object



733
734
735
736
737
738
739
740
741
742
743
# File 'lib/multiarray/complex.rb', line 733

def imag=( value )
  if typecode < COMPLEX_
    decompose( 1 )[] = value
  elsif typecode == OBJECT
    self[] = Hornetseye::lazy do
      real + value * Complex::I
    end
  else
    raise "Cannot assign imaginary values to object of type #{array_type.inspect}"
  end
end

#imag_with_decomposeObject



721
722
723
724
725
726
727
728
729
# File 'lib/multiarray/complex.rb', line 721

def imag_with_decompose
  if typecode == OBJECT or is_a?( Variable )
    imag_without_decompose
  elsif typecode < COMPLEX_
    decompose 1
  else
    Hornetseye::lazy( *shape ) { typecode.new( 0 ) }
  end
end

#inject(initial = nil, options = {}) ⇒ Object



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/multiarray/operations.rb', line 194

def inject( initial = nil, options = {} )
  unless initial.nil?
    initial = Node.match( initial ).new initial unless initial.is_a? Node
    initial_typecode = initial.typecode
  else
    initial_typecode = typecode
  end
  var1 = options[ :var1 ] || Variable.new( initial_typecode )
  var2 = options[ :var2 ] || Variable.new( typecode )
  block = options[ :block ] || yield( var1, var2 )
  if dimension == 0
    if initial
      block.subst( var1 => initial, var2 => self ).simplify
    else
      demand
    end
  else
    index = Variable.new Hornetseye::INDEX( nil )
    value = element( index ).
      inject nil, :block => block, :var1 => var1, :var2 => var2
    Inject.new( value, index, initial, block, var1, var2 ).force
  end
end

#integralObject

alias_method_chain :lut, :composite



450
451
452
453
454
455
456
457
458
459
# File 'lib/multiarray/operations.rb', line 450

def integral
  left = pointer_type.new
  block = Integral.new left, self
  if block.compilable?
    GCCFunction.run block
  else
    block.demand
  end
  left
end

#lut(table, options = {}) ⇒ Object

alias_method_chain :histogram, :composite



404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
# File 'lib/multiarray/operations.rb', line 404

def lut( table, options = {} )
  options = { :safe => true }.merge options
  if options[ :safe ]
    if shape.first != 1 and table.dimension == 1
      source = Hornetseye::lazy( 1 ) { |i| self }.unroll
    else
      if shape.first > table.dimension
        raise "First dimension of array (#{shape.first}) is greater than the " +
              " number of dimensions of LUT (#{table.dimension})"
      end
      source = self
    end
  else
    source = self
  end
  if options[ :safe ]
    for i in 0 ... source.shape.first
      range = source.roll[ i ].range
      if range.begin < 0
        raise "#{i+1}th dimension of index must be in 0 ... #{table.shape[i]} " +
              "(but was #{range.begin})"
      end
      offset = table.dimension - source.shape.first
      if range.end >= table.shape[ i + offset ]
        raise "#{i+1}th dimension of index must be in 0 ... " +
              "#{table.shape[ i + offset ]} (but was #{range.end})"
      end
    end
  end
  if source.dimension <= 1 and variables.empty?
    result = table
    ( table.dimension - 1 ).downto( 0 ) do |i|
      result = result.element source.element( INT.new( i ) ).demand
    end
    result
  else
    Lut.new( source, table, options[ :n ] ).force
  end
end

#maxObject



243
244
245
# File 'lib/multiarray/operations.rb', line 243

def max
  inject { |a,b| a.major b }
end

#minObject



239
240
241
# File 'lib/multiarray/operations.rb', line 239

def min
  inject { |a,b| a.minor b }
end

#normalise(range = 0 .. 0xFF) ⇒ Object



255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/multiarray/operations.rb', line 255

def normalise( range = 0 .. 0xFF )
  if range.exclude_end?
    raise "Normalisation does not support ranges with end value " +
          "excluded (such as #{range})"
  end
  lower, upper = min, max
  if lower.is_a? RGB or upper.is_a? RGB
    current = [ lower.r, lower.g, lower.b ].min ..
              [ upper.r, upper.g, upper.b ].max
  else
    current = min .. max
  end
  if current.last != current.first
    factor =
      ( range.last - range.first ).to_f / ( current.last - current.first )
    self * factor + ( range.first - current.first * factor )
  else
    self + ( range.first - current.first )
  end
end

#product(filter) ⇒ Node

Compute product table from two arrays

Used internally to implement convolutions.

Parameters:

  • filter (Node)

    Filter to form product table with.

Returns:

  • (Node)

    Result of operation.

See Also:



335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/multiarray/operations.rb', line 335

def product( filter )
  filter = Node.match( filter, typecode ).new filter unless filter.is_a? Node
  if dimension != filter.dimension
    raise "Filter has #{filter.dimension} dimension(s) but should " +
          "have #{dimension}"
  end
  if dimension == 0
    self * filter
  else
    Hornetseye::lazy { |i,j| self[j].product filter[i] }
  end
end

#r=(value) ⇒ Object



470
471
472
473
474
475
476
477
478
479
480
# File 'lib/multiarray/rgb.rb', line 470

def r=( value )
  if typecode < RGB_
    decompose( 0 )[] = value
  elsif typecode == OBJECT
    self[] = Hornetseye::lazy do
      value * RGB.new( 1, 0, 0 ) + g * RGB.new( 0, 1, 0 ) + b * RGB.new( 0, 0, 1 )
    end
  else
    raise "Cannot assign red channel to object of type #{array_type.inspect}"
  end
end

#r_with_decomposeObject



458
459
460
461
462
463
464
465
466
# File 'lib/multiarray/rgb.rb', line 458

def r_with_decompose
  if typecode == OBJECT or is_a?( Variable )
    r_without_decompose
  elsif typecode < RGB_
    decompose 0
  else
    self
  end
end

#rangeObject



251
252
253
# File 'lib/multiarray/operations.rb', line 251

def range
  min .. max
end

#real=(value) ⇒ Object



709
710
711
712
713
714
715
716
717
718
719
# File 'lib/multiarray/complex.rb', line 709

def real=( value )
  if typecode < COMPLEX_
    decompose( 0 )[] = value
  elsif typecode == OBJECT
    self[] = Hornetseye::lazy do
      value + imag * Complex::I
    end
  else
    self[] = value
  end
end

#real_with_decomposeObject



697
698
699
700
701
702
703
704
705
# File 'lib/multiarray/complex.rb', line 697

def real_with_decompose
  if typecode == OBJECT or is_a?( Variable )
    real_without_decompose
  elsif typecode < COMPLEX_
    decompose 0
  else
    self
  end
end

#roll(n = 1) ⇒ Object



165
166
167
168
169
170
171
172
173
# File 'lib/multiarray/operations.rb', line 165

def roll( n = 1 )
  if n < 0
    unroll -n
  else
    order = ( 0 ... dimension ).to_a
    n.times { order = order[ 1 .. -1 ] + [ order.first ] }
    transpose *order
  end
end

#sumObject



247
248
249
# File 'lib/multiarray/operations.rb', line 247

def sum
  inject { |a,b| a + b }
end

#to_type(dest) ⇒ Object



94
95
96
97
98
99
100
101
102
103
# File 'lib/multiarray/operations.rb', line 94

def to_type( dest )
  if dimension == 0 and variables.empty?
    target = typecode.to_type dest
    target.new simplify.get
  else
    key = "to_#{dest.to_s.downcase}"
    Hornetseye::ElementWise( lambda { |x| x.to_type dest }, key,
                             lambda { |t| t.to_type dest } ).new( self ).force
  end
end

#to_type_with_rgb(dest) ⇒ Object



105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/multiarray/operations.rb', line 105

def to_type_with_rgb( dest )
  if typecode < RGB_
    if dest < FLOAT_
      lazy { r * 0.299 + g * 0.587 + b * 0.114 }.to_type dest
    elsif dest < INT_
      lazy { ( r * 0.299 + g * 0.587 + b * 0.114 ).round }.to_type dest
    else
      to_type_without_rgb dest
    end
  else
    to_type_without_rgb dest
  end
end

#transpose(*order) ⇒ Node

Lazy transpose of array

Lazily compute transpose by swapping indices according to the specified order.

Parameters:

  • order (Array<Integer>)

    New order of indices.

Returns:

  • (Node)

    Returns the transposed array.



154
155
156
157
158
159
160
161
162
163
# File 'lib/multiarray/operations.rb', line 154

def transpose( *order )
  term = self
  variables = shape.reverse.collect do |i|
    var = Variable.new Hornetseye::INDEX( i )
    term = term.element var
    var
  end.reverse
  order.collect { |o| variables[o] }.
    inject( term ) { |retval,var| Lambda.new var, retval }
end

#unroll(n = 1) ⇒ Object



175
176
177
178
179
180
181
182
183
# File 'lib/multiarray/operations.rb', line 175

def unroll( n = 1 )
  if n < 0
    roll -n
  else
    order = ( 0 ... dimension ).to_a
    n.times { order = [ order.last ] + order[ 0 ... -1 ] }
    transpose *order
  end
end