Class: AxisGroup

Inherits:
Object
  • Object
show all
Defined in:
lib/carray/axis_group.rb

Overview


AxisGroup -- value-independent grouping spec.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(value, slots) ⇒ AxisGroup

Built from CArray#axis_group. value is the shape template; slots is the raw slot list (CACategorical or nil per slot).



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/carray/axis_group.rb', line 45

def initialize (value, slots)
  ndim  = value.ndim
  shape = value.shape
  meta  = []
  cursor = 0

  slots.each do |slot|
    case slot
    when nil
      if cursor >= ndim
        raise IndexError,
              "axis_group: too many slots for ndim #{ndim}"
      end
      meta << { kind: :band, axis: cursor, len: shape[cursor] }
      cursor += 1
    when CACategorical
      rank = slot.ndim
      if cursor + rank > ndim
        raise IndexError,
              "axis_group: categorical of rank #{rank} at axis #{cursor} " \
              "exceeds ndim #{ndim}"
      end
      consumed = (cursor...cursor + rank).to_a
      consumed.each_with_index do |a, j|
        if slot.shape[j] != shape[a]
          raise IndexError,
                "axis_group: categorical axis #{j} length #{slot.shape[j]} " \
                "!= source axis #{a} length #{shape[a]}"
        end
      end
      meta << { kind: :group, axes: consumed, k: slot.labels.size,
                codes: slot.codes, labels: slot.labels }
      cursor += rank
    else
      raise TypeError,
            "axis_group: slot must be a CACategorical or nil " \
            "(got #{slot.class})"
    end
  end

  unless cursor == ndim
    raise IndexError,
          "axis_group: slots cover #{cursor} of #{ndim} axes; all axes must " \
          "be given explicitly (no trailing omission / implicit nil fill)"
  end

  @ndim           = ndim
  @template_shape = shape
  @slot_meta      = meta.freeze
  freeze
end

Instance Attribute Details

#ndimObject (readonly)

Returns the value of attribute ndim.



97
98
99
# File 'lib/carray/axis_group.rb', line 97

def ndim
  @ndim
end

#template_shapeObject (readonly)

Returns the value of attribute template_shape.



97
98
99
# File 'lib/carray/axis_group.rb', line 97

def template_shape
  @template_shape
end

Class Method Details

.parse_axis(axis) ⇒ Object

Parse an axis: spec into [has_group, fused_band_slot_positions].



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/carray/axis_group.rb', line 217

def self.parse_axis (axis)
  has_group = false
  fused = []
  Array(axis).each do |a|
    if a == :group
      has_group = true
    elsif a.is_a?(Integer)
      fused << a
    else
      raise TypeError,
            "axis_group reduce: axis entry must be :group or Integer " \
            "(got #{a.inspect})"
    end
  end
  [has_group, fused]
end

Instance Method Details

#axis_vector(s) ⇒ Object

Per-slot label vector descriptor for GroupLabels. group slot -> [:names, labels_array] band slot -> [:identity, len]



207
208
209
210
211
212
213
214
# File 'lib/carray/axis_group.rb', line 207

def axis_vector (s)
  m = @slot_meta[s]
  if m[:kind] == :group
    [:names, m[:labels]]
  else
    [:identity, m[:len]]
  end
end

#full_labelsObject

Full-space label view (every slot survives, slot order).



200
201
202
# File 'lib/carray/axis_group.rb', line 200

def full_labels
  GroupLabels.new((0...@slot_meta.size).map { |s| axis_vector(s) })
end

#inspectString

Returns:

  • (String)


235
236
237
238
# File 'lib/carray/axis_group.rb', line 235

def inspect
  kinds = @slot_meta.map { |m| m[:kind] == :group ? "g#{m[:k]}" : "band" }
  "#<AxisGroup ndim=#{@ndim} slots=[#{kinds.join(', ')}]>"
end

#labels(*idx, axis: nil) ⇒ Object


labels -- coordinate labels in the SAME index space as a reduced result.

g.labels(i, j, k) -> the block's label tuple (Array); group axis = its label, band axis = its integer index. g.labels(axis: SPEC) -> a GroupLabels view in the index space of value[g].reduce(axis: SPEC) (lockstep). g.labels -> a GroupLabels view of the full grouped space.



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/carray/axis_group.rb', line 181

def labels (*idx, axis: nil)
  unless idx.empty?
    if axis
      raise ArgumentError, "labels: pass either positional index or axis:"
    end
    return full_labels[*idx]
  end
  return full_labels unless axis

  has_group, fused = AxisGroup.parse_axis(axis)
  unless has_group
    raise ArgumentError,
          "labels(axis:) needs :group (labels track the grouped result)"
  end
  survivors = (0...@slot_meta.size).reject { |s| fused.include?(s) }
  GroupLabels.new(survivors.map { |s| axis_vector(s) })
end

#nslotsObject

Number of output slots (= number of slots in the spec; each slot is one output axis before any band reduction).



101
102
103
# File 'lib/carray/axis_group.rb', line 101

def nslots
  @slot_meta.size
end

#reduce_plan(fused) ⇒ Object

Build the reduction plan for the kernel. fused is the list of band SLOT positions to fold into the statistic (= integer axes given alongside :group). Returns [group_axes, bundles, group_dims, perm, squeeze] where group_axes : ascending source axes handed to the kernel as the slab. bundles : [codes, k, consumed_axes] per effective group slot (slot order); a fused band slot becomes a k=1 all-zero bundle. group_dims : the effective group dims (slot order) to reshape the leading K_total axis into. perm : permutation mapping the reshaped layout [*group_dims, *preserved_band_dims] to slot order. squeeze : slot positions (slot order) that are length-1 (fused bands) to drop after the transpose.



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/carray/axis_group.rb', line 124

def reduce_plan (fused)
  fused = Array(fused)
  fused.each do |s|
    m = @slot_meta[s]
    unless m && m[:kind] == :band
      raise IndexError,
            "axis_group reduce: axis #{s} is not a band (held) axis; " \
            "use :group to fold group axes"
    end
  end

  eff_group      = []
  preserved_band = []
  @slot_meta.each_with_index do |m, s|
    if m[:kind] == :group || fused.include?(s)
      eff_group << s
    else
      preserved_band << s
    end
  end

  bundles = eff_group.map do |s|
    m = @slot_meta[s]
    if m[:kind] == :group
      [m[:codes], m[:k], m[:axes]]
    else
      [CArray.int32(m[:len]), 1, [m[:axis]]]
    end
  end

  group_axes = eff_group.flat_map { |s|
    m = @slot_meta[s]
    m[:kind] == :group ? m[:axes] : [m[:axis]]
  }.sort

  group_dims = eff_group.map { |s|
    m = @slot_meta[s]
    m[:kind] == :group ? m[:k] : 1
  }

  gp = eff_group.size
  reshaped_axis = {}
  eff_group.each_with_index      { |s, p| reshaped_axis[s] = p }
  preserved_band.each_with_index { |s, q| reshaped_axis[s] = gp + q }
  perm = (0...@slot_meta.size).map { |s| reshaped_axis[s] }

  [group_axes, bundles, group_dims, perm, fused.sort]
end