carray 3.0.4 → 3.0.6

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 (155) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +438 -0
  3. data/README.md +1 -1
  4. data/carray.gemspec +3 -2
  5. data/ext/ca_array_pool.c +25 -1
  6. data/ext/ca_axis_dispatch.c +19 -20
  7. data/ext/ca_axis_group.c +1 -1
  8. data/ext/ca_categorical_iterator.c +34 -27
  9. data/ext/ca_compare.c +4 -4
  10. data/ext/ca_composite_dispatch.c +9 -9
  11. data/ext/ca_kernel_iterator.c +122 -237
  12. data/ext/ca_kernel_iterator.h +522 -337
  13. data/ext/ca_obj_array.c +69 -19
  14. data/ext/ca_obj_bincmp.c +10 -1
  15. data/ext/ca_obj_binop.c +11 -1
  16. data/ext/ca_obj_bitarray.c +5 -1
  17. data/ext/ca_obj_bitfield.c +60 -43
  18. data/ext/ca_obj_byte_swap.c +4 -1
  19. data/ext/ca_obj_const_string.c +15 -3
  20. data/ext/ca_obj_face.c +44 -1
  21. data/ext/ca_obj_fake.c +3 -1
  22. data/ext/ca_obj_field.c +13 -3
  23. data/ext/ca_obj_grid.c +219 -17
  24. data/ext/ca_obj_meld.c +69 -113
  25. data/ext/ca_obj_moncmp.c +9 -1
  26. data/ext/ca_obj_monop.c +86 -12
  27. data/ext/ca_obj_object.c +118 -32
  28. data/ext/ca_obj_record.c +3 -0
  29. data/ext/ca_obj_reduce.c +19 -1
  30. data/ext/ca_obj_refer.c +80 -26
  31. data/ext/ca_obj_remap.c +69 -10
  32. data/ext/ca_obj_roll.c +27 -6
  33. data/ext/ca_obj_select.c +38 -6
  34. data/ext/ca_obj_select_axis.c +1 -0
  35. data/ext/ca_obj_shift.c +32 -7
  36. data/ext/ca_obj_stack.c +311 -111
  37. data/ext/ca_obj_stride.c +159 -64
  38. data/ext/ca_obj_string.c +27 -2
  39. data/ext/ca_obj_tile.c +28 -7
  40. data/ext/ca_obj_transpose.c +8 -4
  41. data/ext/ca_obj_triop.c +12 -1
  42. data/ext/ca_obj_window.c +80 -50
  43. data/ext/ca_op_ipower.c +12 -12
  44. data/ext/ca_sort_kernels.h +6 -3
  45. data/ext/caframe_csv_reader.c +1 -1
  46. data/ext/carray.h +35 -1
  47. data/ext/carray_access.c +43 -67
  48. data/ext/carray_address_basis.c +134 -0
  49. data/ext/carray_attribute.c +32 -0
  50. data/ext/carray_broadcast.c +3 -4
  51. data/ext/carray_build_flags.h +1 -1
  52. data/ext/carray_cast.c +251 -93
  53. data/ext/carray_cast_func.rb +56 -26
  54. data/ext/carray_conversion.c +11 -7
  55. data/ext/carray_core.c +814 -72
  56. data/ext/carray_count.c +6 -0
  57. data/ext/carray_element.c +22 -13
  58. data/ext/carray_factorize.c +20 -20
  59. data/ext/carray_generate.c +8 -2
  60. data/ext/carray_histogram.c +26 -4
  61. data/ext/carray_index_classifier.c +4 -4
  62. data/ext/carray_internal.h +159 -12
  63. data/ext/carray_kernels_bincmp.c +2174 -170
  64. data/ext/carray_kernels_binop.c +25653 -5487
  65. data/ext/carray_kernels_init.c +8 -8
  66. data/ext/carray_kernels_moncmp.c +96 -96
  67. data/ext/carray_kernels_monop.c +12253 -5235
  68. data/ext/carray_kernels_reduce_aggregate.c +6150 -2423
  69. data/ext/carray_kernels_reduce_boolean.c +1533 -15
  70. data/ext/carray_kernels_reduce_cumulative.c +2442 -882
  71. data/ext/carray_kernels_reduce_extreme.c +5658 -1661
  72. data/ext/carray_kernels_reduce_variance.c +2534 -402
  73. data/ext/carray_kernels_scan.c +368 -368
  74. data/ext/carray_kernels_search.c +4551 -604
  75. data/ext/carray_kernels_sort.c +659 -751
  76. data/ext/carray_kernels_triop.c +114 -114
  77. data/ext/carray_key_windows.c +4 -0
  78. data/ext/carray_lazy.c +47 -0
  79. data/ext/carray_loop.c +1 -1
  80. data/ext/carray_mask.c +175 -161
  81. data/ext/carray_median_percentile.c +4 -4
  82. data/ext/carray_memory_view.c +15 -9
  83. data/ext/carray_operator.c +152 -49
  84. data/ext/carray_order.c +230 -10
  85. data/ext/carray_random.c +22 -19
  86. data/ext/carray_scatter.c +24 -7
  87. data/ext/carray_slab.c +128 -29
  88. data/ext/carray_sort.c +224 -16
  89. data/ext/carray_sort_kernel.c +5 -7
  90. data/ext/carray_struct.c +35 -43
  91. data/ext/carray_test.c +74 -19
  92. data/ext/carray_time_text.c +6 -4
  93. data/ext/carray_utils.c +63 -21
  94. data/ext/extconf.rb +11 -0
  95. data/ext/mkkernel.rb +546 -199
  96. data/ext/ruby_carray.c +4 -1
  97. data/ext/version.h +4 -4
  98. data/lib/carray/attribute.rb +42 -5
  99. data/lib/carray/autoload_carray.rb +3 -3
  100. data/lib/carray/autoload_method_extension.rb +5 -3
  101. data/lib/carray/axis_group.rb +13 -7
  102. data/lib/carray/basics.rb +28 -8
  103. data/lib/carray/block_iterator.rb +17 -3
  104. data/lib/carray/categorical.rb +35 -23
  105. data/lib/carray/categorical_iterator.rb +8 -8
  106. data/lib/carray/complex.rb +2 -2
  107. data/lib/carray/conditional.rb +3 -2
  108. data/lib/carray/const_string.rb +10 -0
  109. data/lib/carray/data_type_extension.rb +8 -1
  110. data/lib/carray/fixlen_string.rb +41 -2
  111. data/lib/carray/frame/column_verbs.rb +48 -16
  112. data/lib/carray/frame/concat.rb +17 -3
  113. data/lib/carray/frame/convert.rb +19 -0
  114. data/lib/carray/frame/csv_parser.rb +32 -32
  115. data/lib/carray/frame/describe.rb +4 -2
  116. data/lib/carray/frame/frame.rb +405 -85
  117. data/lib/carray/frame/group.rb +777 -64
  118. data/lib/carray/frame/io.rb +212 -72
  119. data/lib/carray/frame/join.rb +146 -37
  120. data/lib/carray/frame/records.rb +3 -3
  121. data/lib/carray/frame/reshape.rb +89 -11
  122. data/lib/carray/frame/sort.rb +57 -9
  123. data/lib/carray/frame/verbs.rb +384 -49
  124. data/lib/carray/frame/windows.rb +44 -6
  125. data/lib/carray/fuse_source.rb +31 -2
  126. data/lib/carray/inspect.rb +7 -2
  127. data/lib/carray/iterator.rb +32 -0
  128. data/lib/carray/lazy.rb +52 -8
  129. data/lib/carray/mask_gap_fill.rb +37 -17
  130. data/lib/carray/meld_reduce.rb +1 -1
  131. data/lib/carray/methods/choose.rb +3 -2
  132. data/lib/carray/methods/is_in.rb +8 -1
  133. data/lib/carray/methods/meshgrid.rb +3 -1
  134. data/lib/carray/runtime.rb +47 -8
  135. data/lib/carray/segment_iterator.rb +35 -16
  136. data/lib/carray/serialize.rb +53 -19
  137. data/lib/carray/stack.rb +33 -18
  138. data/lib/carray/string.rb +8 -0
  139. data/lib/carray/string_operation_extension.rb +13 -5
  140. data/lib/carray/struct_builder.rb +5 -3
  141. data/lib/carray/time.rb +112 -68
  142. data/lib/carray/window_iterator.rb +16 -4
  143. data/yard-stubs/ca_obj_array.rb +26 -1
  144. data/yard-stubs/ca_obj_transpose.rb +5 -4
  145. data/yard-stubs/carray_attribute.rb +3 -1
  146. data/yard-stubs/carray_cast.rb +10 -5
  147. data/yard-stubs/carray_conversion.rb +13 -3
  148. data/yard-stubs/carray_element.rb +7 -6
  149. data/yard-stubs/carray_mask.rb +28 -4
  150. data/yard-stubs/carray_math.rb +45 -20
  151. data/yard-stubs/carray_memory_view.rb +7 -0
  152. data/yard-stubs/carray_order.rb +100 -4
  153. data/yard-stubs/carray_scatter.rb +7 -5
  154. data/yard-stubs/carray_test.rb +7 -4
  155. metadata +18 -4
data/lib/carray/time.rb CHANGED
@@ -113,6 +113,13 @@ module CATimeUnitAlgebra
113
113
  end
114
114
  end
115
115
 
116
+ # How a refusal names an operand: a Face by its class, any other array as
117
+ # a CArray (a lazy expression's node classes are not the user's), anything
118
+ # else by its class.
119
+ def operand_name(x)
120
+ ( x.is_a?(CArray) && !x.face? ) ? "CArray" : x.class.to_s
121
+ end
122
+
116
123
  # A single value an operator meets an array with, as a one-cell array
117
124
  # marked scalar (a CScalar) of its kind, or nil when it is not one: a
118
125
  # duration -- a CATimedelta::Element, or a String that reads as one
@@ -138,6 +145,20 @@ module CATimeUnitAlgebra
138
145
  end
139
146
  end
140
147
 
148
+ # A duration String added to or subtracted from a time, as one step of its
149
+ # own resolution: "15 minutes" is one 15-minute step, the duration
150
+ # CArray.int64(1).timedelta(unit: "15 minutes") is, so it lands on a
151
+ # "10 minutes" grid or is refused in the same way. nil for anything else.
152
+ def step_operand(x)
153
+ return nil unless x.is_a?(String)
154
+ res = begin
155
+ CATime::Resolution.parse(x)
156
+ rescue ArgumentError
157
+ return nil
158
+ end
159
+ CATimedelta.wrap(CScalar.int64 { 1 }, unit: res)
160
+ end
161
+
141
162
  def scalar_instant(t, unit)
142
163
  v = begin
143
164
  convert_instant!(t.parent, t.unit, unit)[0]
@@ -1071,7 +1092,7 @@ class CATime
1071
1092
  # resolution (see §4 of docs/CATime.md).
1072
1093
  # @return [CArray]
1073
1094
  def ticks
1074
- parent
1095
+ face_storage
1075
1096
  end
1076
1097
 
1077
1098
  # @overload to_unit(unit)
@@ -1101,7 +1122,10 @@ class CATime
1101
1122
  # @raise [RangeError] when the widened ticks overflow int64.
1102
1123
  def to_unit(unit)
1103
1124
  to = CATime::Resolution.parse(unit)
1104
- CATimeUnitAlgebra.convert_instant_floor(parent, self.unit, to).time(unit: to)
1125
+ # The conversion hands back the storage itself when the unit does not
1126
+ # change; the result is a new array whichever unit is asked for.
1127
+ return face_storage.copy.time(unit: to) if to == self.unit
1128
+ CATimeUnitAlgebra.convert_instant_floor(face_storage, self.unit, to).time(unit: to)
1105
1129
  end
1106
1130
 
1107
1131
  # @overload +(other)
@@ -1116,7 +1140,9 @@ class CATime
1116
1140
  # into whole ticks; on any other (`:W`, `"3 days"`) a month raises, as a
1117
1141
  # fixed duration on a month or year grid does. Adding two datetimes is
1118
1142
  # ill-defined. A single duration -- a {CATimedelta::Element} or a String
1119
- # such as `"3 days"` -- is added to every cell.
1143
+ # such as `"3 days"` -- is added to every cell; a String is one step of
1144
+ # its own resolution, so `"15 minutes"` on a `"10 minutes"` grid raises
1145
+ # as `CArray.int64(1).timedelta(unit: "15 minutes")` does.
1120
1146
  # @param other [CATimedelta, CATimedelta::Element, String]
1121
1147
  # (or a MemoryView producer whose +to_ca+ gives one, such as an Arrow
1122
1148
  # duration array)
@@ -1126,17 +1152,18 @@ class CATime
1126
1152
  # @raise [RangeError] when a result leaves the range of int64 ticks.
1127
1153
  def +(other)
1128
1154
  given, other = other, CATimeUnitAlgebra.operand(other)
1129
- other = CATimeUnitAlgebra.scalar_operand(other, unit) || other
1155
+ other = CATimeUnitAlgebra.step_operand(other) ||
1156
+ CATimeUnitAlgebra.scalar_operand(other, unit) || other
1130
1157
  case other
1131
1158
  when CATimedelta
1132
1159
  return shifted_by_months(other, 1) if moved_by_months?(other)
1133
1160
  d = CATimeUnitAlgebra.duration_on_grid(other.parent, other.unit, unit)
1134
1161
  d = d[0] if other.scalar?
1135
- CATimeUnitAlgebra.checked_add(parent, d).time(unit: unit)
1162
+ CATimeUnitAlgebra.checked_add(face_storage, d).time(unit: unit)
1136
1163
  when CATime
1137
1164
  raise TypeError, "CATime + CATime is ill-defined"
1138
1165
  else
1139
- raise TypeError, "CATime + #{given.class} is not allowed: add a duration " \
1166
+ raise TypeError, "CATime + #{CATimeUnitAlgebra.operand_name(given)} is not allowed: add a duration " \
1140
1167
  "(a CATimedelta, a CATimedelta::Element or a String such as \"3 days\")"
1141
1168
  end
1142
1169
  end
@@ -1155,21 +1182,22 @@ class CATime
1155
1182
  # @raise [TypeError] on an unsupported operand.
1156
1183
  def -(other)
1157
1184
  given, other = other, CATimeUnitAlgebra.operand(other)
1158
- other = CATimeUnitAlgebra.scalar_operand(other, unit) || other
1185
+ other = CATimeUnitAlgebra.step_operand(other) ||
1186
+ CATimeUnitAlgebra.scalar_operand(other, unit) || other
1159
1187
  case other
1160
1188
  when CATimedelta
1161
1189
  return shifted_by_months(other, -1) if moved_by_months?(other)
1162
1190
  d = CATimeUnitAlgebra.duration_on_grid(other.parent, other.unit, unit)
1163
1191
  d = d[0] if other.scalar?
1164
- CATimeUnitAlgebra.checked_sub(parent, d).time(unit: unit)
1192
+ CATimeUnitAlgebra.checked_sub(face_storage, d).time(unit: unit)
1165
1193
  when CATime
1166
1194
  u = CATimeUnitAlgebra.diff_unit(unit, other.unit)
1167
- a = CATimeUnitAlgebra.convert_instant!(parent, unit, u)
1195
+ a = CATimeUnitAlgebra.convert_instant!(face_storage, unit, u)
1168
1196
  b = CATimeUnitAlgebra.convert_instant!(other.parent, other.unit, u)
1169
1197
  b = b[0] if other.scalar?
1170
1198
  CATimeUnitAlgebra.checked_sub(a, b).timedelta(unit: u)
1171
1199
  else
1172
- raise TypeError, "CATime - #{given.class} is not allowed: subtract a duration " \
1200
+ raise TypeError, "CATime - #{CATimeUnitAlgebra.operand_name(given)} is not allowed: subtract a duration " \
1173
1201
  "(a CATimedelta, a CATimedelta::Element or a String such as \"3 days\") " \
1174
1202
  "or a time (a CATime, a CATime::Element, a Time, a Date or a String)"
1175
1203
  end
@@ -1189,7 +1217,7 @@ class CATime
1189
1217
  private def shifted_by_months (td, sign)
1190
1218
  months = td.parent.int64 * (sign * CATimeUnitAlgebra.months_per_tick(td.unit))
1191
1219
  months = months[0] if td.scalar?
1192
- CATimeUnitAlgebra.shift_months(parent, unit, months).time(unit: unit)
1220
+ CATimeUnitAlgebra.shift_months(face_storage, unit, months).time(unit: unit)
1193
1221
  end
1194
1222
 
1195
1223
  # @overload pmax(other)
@@ -1255,8 +1283,9 @@ class CATime
1255
1283
  # they run on the storage ticks and round the result back to the nearest
1256
1284
  # tick of `unit`. A caller who needs a finer answer declares the grid
1257
1285
  # first (`t.to_unit(:ms).mean`) -- the same rule linear_fetch follows.
1258
- # Rounding is to nearest, not toward zero: a truncating round would flip
1259
- # the direction of the error at the epoch.
1286
+ # Rounding is to nearest, and a tie goes to the later tick: rounding half
1287
+ # away from zero would send a tie later after 1970 and earlier before it,
1288
+ # so moving the data by whole days would change the answer.
1260
1289
 
1261
1290
  # @overload mean(axis: nil, **opts)
1262
1291
  # Returns the centroid time on `self`'s unit (§8), rounded to the
@@ -1492,7 +1521,7 @@ class CATime
1492
1521
  # @return [Element, CATime] an {Element} for a scalar `addr`, `nil` when
1493
1522
  # that single answer is undetermined.
1494
1523
  def linear_fetch (addr, **opts)
1495
- r = parent.float64.linear_fetch(addr, **opts)
1524
+ r = face_storage.float64.linear_fetch(addr, **opts)
1496
1525
  case r
1497
1526
  when CArray
1498
1527
  # mask_invalid before the int64 cast: the cast turns a NaN into 0, which
@@ -1523,7 +1552,7 @@ class CATime
1523
1552
  def to_time
1524
1553
  if CATimeUnitAlgebra::FIXED.key?(unit.base)
1525
1554
  f = unit.tick_ratio # exact seconds / tick (Rational)
1526
- parent.convert(:object) {|v| Time.at(v * f, in: 'UTC')}
1555
+ face_storage.convert(:object) {|v| Time.at(v * f, in: 'UTC')}
1527
1556
  else # calendar: exact granule midnight
1528
1557
  (days_since_epoch * 86400).convert(:object) {|v| Time.at(v, in: 'UTC')}
1529
1558
  end
@@ -1557,8 +1586,8 @@ class CATime
1557
1586
  # @return [CArray]
1558
1587
  def year
1559
1588
  case unit.base
1560
- when :Y then parent * unit.count + 1970
1561
- when :M then (parent * unit.count + 1970 * 12) / 12
1589
+ when :Y then face_storage * unit.count + 1970
1590
+ when :M then (face_storage * unit.count + 1970 * 12) / 12
1562
1591
  else CATimeCivil.civil_from_days(days_since_epoch)[0]
1563
1592
  end
1564
1593
  end
@@ -1568,9 +1597,9 @@ class CATime
1568
1597
  # @return [CArray] integer.
1569
1598
  def month
1570
1599
  case unit.base
1571
- when :Y then parent * 0 + 1
1600
+ when :Y then face_storage * 0 + 1
1572
1601
  when :M
1573
- mo = parent * unit.count + 1970 * 12
1602
+ mo = face_storage * unit.count + 1970 * 12
1574
1603
  mo % 12 + 1
1575
1604
  else CATimeCivil.civil_from_days(days_since_epoch)[1]
1576
1605
  end
@@ -1581,7 +1610,7 @@ class CATime
1581
1610
  # @return [CArray] integer.
1582
1611
  def day
1583
1612
  case unit.base
1584
- when :Y, :M then parent * 0 + 1
1613
+ when :Y, :M then face_storage * 0 + 1
1585
1614
  else CATimeCivil.civil_from_days(days_since_epoch)[2]
1586
1615
  end
1587
1616
  end
@@ -1704,7 +1733,7 @@ class CATime
1704
1733
  # writes to_unit(:D) first) -- and the result is rounded back to a tick.
1705
1734
  def reduce_on_own_grid(op, kind, args, opts)
1706
1735
  with_surface_fill(opts) do |o|
1707
- round_and_relift(parent.public_send(op, *args, **o), kind)
1736
+ round_and_relift(face_storage.public_send(op, *args, **o), kind)
1708
1737
  end
1709
1738
  end
1710
1739
 
@@ -1714,14 +1743,15 @@ class CATime
1714
1743
  def round_and_relift(r, kind)
1715
1744
  case r
1716
1745
  when Numeric
1717
- v = r.round
1746
+ v = kind == :timedelta ? r.round : (r + 0.5).floor
1718
1747
  kind == :timedelta ? CATimedelta::Element.new(v, unit) : Element.new(v, unit)
1719
1748
  when CArray
1720
1749
  # A cell with no answer arrives masked and the mask survives the cast.
1721
1750
  # NaN does not: it has no int64 image, so a `fill_value: Float::NAN`
1722
1751
  # cell would cast to garbage bits. mask_invalid turns it into UNDEF,
1723
1752
  # which is what "no answer" means here.
1724
- iv = r.round.mask_invalid.int64
1753
+ near = kind == :timedelta ? r.round : (r + 0.5).floor
1754
+ iv = near.mask_invalid.int64
1725
1755
  kind == :timedelta ? iv.timedelta(unit: unit) : iv.time(unit: unit)
1726
1756
  when Array
1727
1757
  r.map {|x| round_and_relift(x, kind)}
@@ -1781,7 +1811,7 @@ class CATimedelta
1781
1811
  # measured in this array's resolution (see {CATime#ticks}).
1782
1812
  # @return [CArray]
1783
1813
  def ticks
1784
- parent
1814
+ face_storage
1785
1815
  end
1786
1816
 
1787
1817
  # @overload to_unit(unit)
@@ -1803,7 +1833,9 @@ class CATimedelta
1803
1833
  # @raise [RangeError] when the widened ticks overflow int64.
1804
1834
  def to_unit(unit)
1805
1835
  to = CATime::Resolution.parse(unit)
1806
- CATimeUnitAlgebra.convert_scale_trunc(parent, self.unit, to).timedelta(unit: to)
1836
+ # As CATime#to_unit: a new array even when the unit does not change.
1837
+ return face_storage.copy.timedelta(unit: to) if to == self.unit
1838
+ CATimeUnitAlgebra.convert_scale_trunc(face_storage, self.unit, to).timedelta(unit: to)
1807
1839
  end
1808
1840
 
1809
1841
  # @overload +(other)
@@ -1821,16 +1853,16 @@ class CATimedelta
1821
1853
  case other
1822
1854
  when CATimedelta
1823
1855
  u = common_duration_unit(other.unit)
1824
- a = CATimeUnitAlgebra.convert_scale!(parent, unit, u)
1856
+ a = CATimeUnitAlgebra.convert_scale!(face_storage, unit, u)
1825
1857
  b = CATimeUnitAlgebra.convert_scale!(other.parent, other.unit, u)
1826
1858
  b = b[0] if other.scalar?
1827
1859
  CATimeUnitAlgebra.checked_add(a, b).timedelta(unit: u)
1828
1860
  when CATime
1829
1861
  return other + self unless other.scalar? # commutative -> time's unit
1830
- d = CATimeUnitAlgebra.duration_on_grid(parent, unit, other.unit)
1862
+ d = CATimeUnitAlgebra.duration_on_grid(face_storage, unit, other.unit)
1831
1863
  CATimeUnitAlgebra.checked_add(d, other.parent[0]).time(unit: other.unit)
1832
1864
  else
1833
- raise TypeError, "CATimedelta + #{given.class} is not allowed"
1865
+ raise TypeError, "CATimedelta + #{CATimeUnitAlgebra.operand_name(given)} is not allowed"
1834
1866
  end
1835
1867
  end
1836
1868
 
@@ -1848,12 +1880,12 @@ class CATimedelta
1848
1880
  case other
1849
1881
  when CATimedelta
1850
1882
  u = common_duration_unit(other.unit)
1851
- a = CATimeUnitAlgebra.convert_scale!(parent, unit, u)
1883
+ a = CATimeUnitAlgebra.convert_scale!(face_storage, unit, u)
1852
1884
  b = CATimeUnitAlgebra.convert_scale!(other.parent, other.unit, u)
1853
1885
  b = b[0] if other.scalar?
1854
1886
  CATimeUnitAlgebra.checked_sub(a, b).timedelta(unit: u)
1855
1887
  else
1856
- raise TypeError, "CATimedelta - #{given.class} is not allowed"
1888
+ raise TypeError, "CATimedelta - #{CATimeUnitAlgebra.operand_name(given)} is not allowed"
1857
1889
  end
1858
1890
  end
1859
1891
 
@@ -1862,14 +1894,14 @@ class CATimedelta
1862
1894
  # unit.
1863
1895
  # @return [CATimedelta]
1864
1896
  def abs
1865
- parent.abs.timedelta(unit: unit)
1897
+ face_storage.abs.timedelta(unit: unit)
1866
1898
  end
1867
1899
 
1868
1900
  # @overload -@
1869
1901
  # Returns each duration with its sign reversed, on the same unit.
1870
1902
  # @return [CATimedelta]
1871
1903
  def -@
1872
- (-parent).timedelta(unit: unit)
1904
+ (-face_storage).timedelta(unit: unit)
1873
1905
  end
1874
1906
 
1875
1907
  # @overload *(other)
@@ -1879,8 +1911,8 @@ class CATimedelta
1879
1911
  # @raise [TypeError] when `other` is not an Integer.
1880
1912
  def *(other)
1881
1913
  case other
1882
- when Integer then CATimeUnitAlgebra.checked_mul(parent, other).timedelta(unit: unit)
1883
- else raise TypeError, "CATimedelta * #{other.class} is not allowed (Integer only)"
1914
+ when Integer then CATimeUnitAlgebra.checked_mul(face_storage, other).timedelta(unit: unit)
1915
+ else raise TypeError, "CATimedelta * #{CATimeUnitAlgebra.operand_name(other)} is not allowed (Integer only)"
1884
1916
  end
1885
1917
  end
1886
1918
 
@@ -1898,13 +1930,13 @@ class CATimedelta
1898
1930
  given, other = other, CATimeUnitAlgebra.operand(other)
1899
1931
  case other
1900
1932
  when Integer
1901
- CATimeUnitAlgebra.trunc_divide(parent, other).timedelta(unit: unit)
1933
+ CATimeUnitAlgebra.trunc_divide(face_storage, other).timedelta(unit: unit)
1902
1934
  when CATimedelta
1903
1935
  # A ratio of durations is a number, not a count of whole ticks: 90 s /
1904
1936
  # 60 s is 1.5. Both are brought to one unit first, so the ratio does
1905
1937
  # not depend on the units they are held in.
1906
1938
  u = common_duration_unit(other.unit)
1907
- CATimeUnitAlgebra.convert_scale!(parent, unit, u).to_type(:float64) /
1939
+ CATimeUnitAlgebra.convert_scale!(face_storage, unit, u).to_type(:float64) /
1908
1940
  CATimeUnitAlgebra.convert_scale!(other.parent, other.unit, u)
1909
1941
  else raise TypeError, "CATimedelta / #{given.class} is not allowed"
1910
1942
  end
@@ -1926,7 +1958,7 @@ class CATimedelta
1926
1958
  # reduction, if any of its cells does not).
1927
1959
  def sum(*args, **opts)
1928
1960
  with_surface_fill(opts) { |o|
1929
- exact_sum_relift(parent.accumulate(*args, **o), parent.sum(*args, **o))
1961
+ exact_sum_relift(face_storage.accumulate(*args, **o), face_storage.sum(*args, **o))
1930
1962
  }
1931
1963
  end
1932
1964
 
@@ -1955,7 +1987,7 @@ class CATimedelta
1955
1987
  # the same answer when it fits and raises RangeError when it does not.
1956
1988
  # @return [Element, CATimedelta]
1957
1989
  def accumulate(*args, **opts)
1958
- with_surface_fill(opts) { |o| round_and_relift(parent.accumulate(*args, **o)) }
1990
+ with_surface_fill(opts) { |o| round_and_relift(face_storage.accumulate(*args, **o)) }
1959
1991
  end
1960
1992
 
1961
1993
  # @overload cumsum(axis: nil)
@@ -1966,8 +1998,8 @@ class CATimedelta
1966
1998
  # @return [CATimedelta]
1967
1999
  # @raise [RangeError] if any cell of the running sum does not fit int64.
1968
2000
  def cumsum(*args, **opts)
1969
- exact_sum_relift(parent.send(:cumsum_in_own_type, *args, **opts),
1970
- parent.cumsum(*args, **opts))
2001
+ exact_sum_relift(face_storage.send(:cumsum_in_own_type, *args, **opts),
2002
+ face_storage.cumsum(*args, **opts))
1971
2003
  end
1972
2004
 
1973
2005
  # @overload prod(*)
@@ -1985,7 +2017,7 @@ class CATimedelta
1985
2017
  # Returns the mean duration rounded to the nearest unit count.
1986
2018
  # @return [Element, CATimedelta]
1987
2019
  def mean(*args, **opts)
1988
- with_surface_fill(opts) { |o| round_and_relift(parent.mean(*args, **o)) }
2020
+ with_surface_fill(opts) { |o| round_and_relift(face_storage.mean(*args, **o)) }
1989
2021
  end
1990
2022
 
1991
2023
  # median / percentile / quantile / stddev / stddevp report on this array's
@@ -1997,7 +2029,7 @@ class CATimedelta
1997
2029
  # Returns the median duration on `self`'s unit.
1998
2030
  # @return [Element, CATimedelta]
1999
2031
  def median(*args, **opts)
2000
- with_surface_fill(opts) { |o| round_and_relift(parent.median(*args, **o)) }
2032
+ with_surface_fill(opts) { |o| round_and_relift(face_storage.median(*args, **o)) }
2001
2033
  end
2002
2034
 
2003
2035
  # @overload percentile(*p, axis: nil, **opts)
@@ -2006,7 +2038,7 @@ class CATimedelta
2006
2038
  # two or more give an Array).
2007
2039
  # @return [Element, CATimedelta, Array<Element>, Array<CATimedelta>]
2008
2040
  def percentile(*args, **opts)
2009
- with_surface_fill(opts) { |o| round_and_relift(parent.percentile(*args, **o)) }
2041
+ with_surface_fill(opts) { |o| round_and_relift(face_storage.percentile(*args, **o)) }
2010
2042
  end
2011
2043
 
2012
2044
  # @overload quantile(axis: nil, **opts)
@@ -2014,7 +2046,7 @@ class CATimedelta
2014
2046
  # `self`'s unit.
2015
2047
  # @return [Array<Element>, Array<CATimedelta>]
2016
2048
  def quantile(*args, **opts)
2017
- with_surface_fill(opts) { |o| round_and_relift(parent.quantile(*args, **o)) }
2049
+ with_surface_fill(opts) { |o| round_and_relift(face_storage.quantile(*args, **o)) }
2018
2050
  end
2019
2051
 
2020
2052
  # @overload stddev(axis: nil, **opts)
@@ -2023,7 +2055,7 @@ class CATimedelta
2023
2055
  # the precision matters.
2024
2056
  # @return [Element, CATimedelta]
2025
2057
  def stddev(*args, **opts)
2026
- with_surface_fill(opts) { |o| round_and_relift(parent.stddev(*args, **o)) }
2058
+ with_surface_fill(opts) { |o| round_and_relift(face_storage.stddev(*args, **o)) }
2027
2059
  end
2028
2060
 
2029
2061
  # @overload stddevp(axis: nil, **opts)
@@ -2031,7 +2063,7 @@ class CATimedelta
2031
2063
  # {#stddev}.
2032
2064
  # @return [Element, CATimedelta]
2033
2065
  def stddevp(*args, **opts)
2034
- with_surface_fill(opts) { |o| round_and_relift(parent.stddevp(*args, **o)) }
2066
+ with_surface_fill(opts) { |o| round_and_relift(face_storage.stddevp(*args, **o)) }
2035
2067
  end
2036
2068
 
2037
2069
  def round_and_relift(r)
@@ -2168,7 +2200,7 @@ class CATimedelta
2168
2200
  # @return [Element, CATimedelta] an {Element} for a scalar `addr`, `nil`
2169
2201
  # when that single answer is undetermined.
2170
2202
  def linear_fetch (addr, **opts)
2171
- r = parent.float64.linear_fetch(addr, **opts)
2203
+ r = face_storage.float64.linear_fetch(addr, **opts)
2172
2204
  case r
2173
2205
  when CArray then r.mask_invalid.round.int64.timedelta(unit: unit)
2174
2206
  when Numeric then r.to_f.nan? ? nil : Element.new(r.round, unit)
@@ -3464,8 +3496,18 @@ class CATime
3464
3496
  fl = o + q * step_ticks
3465
3497
  return bucket_head_as_time(fl + step_ticks * n.ne(fl), mul, su)
3466
3498
  when :round
3467
- r = d - q * step_ticks # 0 <= r < step_ticks
3468
- q = q + r.ge((step_ticks + 1) / 2) # ties -> future; no 2*d / 2*r
3499
+ if mul == 1 and [:M, :Y].include?(su.base)
3500
+ # A month or year tick is the start of its month or year, so the
3501
+ # nearest head is by the instant, measured in days.
3502
+ fl = o + q * step_ticks
3503
+ here = days_since_epoch
3504
+ lo = fl.time(unit: su).send(:days_since_epoch)
3505
+ hi = (fl + step_ticks).time(unit: su).send(:days_since_epoch)
3506
+ q = q + (hi - here).le(here - lo) # ties -> future
3507
+ else
3508
+ r = d - q * step_ticks # 0 <= r < step_ticks
3509
+ q = q + r.ge((step_ticks + 1) / 2) # ties -> future; no 2*d / 2*r
3510
+ end
3469
3511
  end
3470
3512
  bucket_head_as_time(o + q * step_ticks, mul, su)
3471
3513
  end
@@ -3517,8 +3559,10 @@ class CATime
3517
3559
  # @overload round(grid = nil, unit:, origin: nil)
3518
3560
  # {#snap} with `direction: :round` -- each element at its nearest bucket
3519
3561
  # head, ties toward the future. Exact for odd `step_ticks` (no half-tick
3520
- # loss). For a calendar bucket the nearest head is by absolute tick
3521
- # distance (month lengths vary), ties toward the future.
3562
+ # loss). For a calendar bucket the nearest head is by the instant (month
3563
+ # lengths vary), ties toward the future. A month or year storage tick
3564
+ # stands for the start of its month or year, so a `:M` array rounds as
3565
+ # the same dates on `:D` do: 1889-07 to the year is 1889.
3522
3566
  # @return [CATime]
3523
3567
  def round(grid = nil, unit: nil, origin: nil)
3524
3568
  snap(grid, unit: unit, origin: origin, direction: :round)
@@ -3604,7 +3648,7 @@ class CATime
3604
3648
 
3605
3649
  # Storage ticks lifted into the numerator grid (see {#resolve_timestep_grid}).
3606
3650
  def storage_ticks_in_numerator_grid(mul)
3607
- mul == 1 ? parent : CATimeUnitAlgebra.widen(parent, mul)
3651
+ mul == 1 ? face_storage : CATimeUnitAlgebra.widen(face_storage, mul)
3608
3652
  end
3609
3653
 
3610
3654
  # A bucket head computed in numerator ticks, back as a {CATime} in the
@@ -3638,15 +3682,15 @@ class CATime
3638
3682
  # an empty array, or one whose every cell is masked.
3639
3683
  def raise_if_int64_overflow(su, mul, o, step_ticks = nil, kind = nil)
3640
3684
  return unless mul > 1 or FINE_UNITS.include?(su.base)
3641
- min = parent.min
3685
+ min = face_storage.min
3642
3686
  return if min == UNDEF
3643
3687
  lo = Integer(min) * mul
3644
- hi = Integer(parent.max) * mul
3688
+ hi = Integer(face_storage.max) * mul
3645
3689
  chk = ->(v, w) { CATimeGrid.check_int64_range(v, w) }
3646
3690
  chk.(lo, "ticks in bucket resolution")
3647
3691
  chk.(hi, "ticks in bucket resolution")
3648
- chk.(lo - o, "parent - origin")
3649
- chk.(hi - o, "parent - origin")
3692
+ chk.(lo - o, "face_storage - origin")
3693
+ chk.(hi - o, "face_storage - origin")
3650
3694
  if kind
3651
3695
  chk.(bucket_head_ticks(lo, o, step_ticks, kind), "bucket head")
3652
3696
  chk.(bucket_head_ticks(hi, o, step_ticks, kind), "bucket head")
@@ -3674,22 +3718,22 @@ class CATime
3674
3718
  case res.base
3675
3719
  when :Y
3676
3720
  ones = CArray.int64(*shape) { 1 }
3677
- CATimeCivil.days_from_civil(parent * res.count + 1970, ones, ones)
3721
+ CATimeCivil.days_from_civil(face_storage * res.count + 1970, ones, ones)
3678
3722
  when :M
3679
- mo = parent * res.count + 1970 * 12
3723
+ mo = face_storage * res.count + 1970 * 12
3680
3724
  y = mo / 12
3681
3725
  m = mo - y * 12 + 1
3682
3726
  CATimeCivil.days_from_civil(y, m, CArray.int64(*shape) { 1 })
3683
- when :W then parent * res.count * 7
3684
- when :D then parent * res.count
3727
+ when :W then face_storage * res.count * 7
3728
+ when :D then face_storage * res.count
3685
3729
  else # fixed sub-day storage
3686
3730
  day = CATime::Resolution.new(1, :D)
3687
3731
  r = CATimeUnitAlgebra.ratio(day, res) # storage ticks per day
3688
3732
  if r.denominator == 1
3689
- parent / r.numerator
3733
+ face_storage / r.numerator
3690
3734
  else # non-day-aligned tick
3691
3735
  tr = res.tick_ratio # seconds / tick
3692
- parent * tr.numerator / (86400 * tr.denominator)
3736
+ face_storage * tr.numerator / (86400 * tr.denominator)
3693
3737
  end
3694
3738
  end
3695
3739
  end
@@ -3702,9 +3746,9 @@ class CATime
3702
3746
  # calendar-storage element has no intra-day field, so it collapses to 0.
3703
3747
  def clock_field(fu)
3704
3748
  r = CATimeUnitAlgebra.ratio(unit, CATime::Resolution.new(1, fu)) # fu ticks / storage tick
3705
- return parent * 0 if r.nil? # calendar storage: no clock field
3706
- fi = r.denominator == 1 ? parent * r.numerator # epoch-relative fu index
3707
- : parent * r.numerator / r.denominator
3749
+ return face_storage * 0 if r.nil? # calendar storage: no clock field
3750
+ fi = r.denominator == 1 ? face_storage * r.numerator # epoch-relative fu index
3751
+ : face_storage * r.numerator / r.denominator
3708
3752
  cycle = fu == :h ? 24 : 60 # hour 0..23; min/sec 0..59
3709
3753
  fi % cycle
3710
3754
  end
@@ -3725,12 +3769,12 @@ class CATime
3725
3769
  return k if kind == :index
3726
3770
  head = CATimeGrid.calendar_bucket_head_ticks(k, st, su, origin)
3727
3771
  return head.time(unit: su) if kind == :floor
3728
- return parent.eq(head) if kind == :on
3772
+ return face_storage.eq(head) if kind == :on
3729
3773
  head_next = CATimeGrid.calendar_bucket_head_ticks(k + 1, st, su, origin)
3730
3774
  sel = # boolean 0/1: pick head_next?
3731
3775
  case kind
3732
- when :ceil then parent.ne(head) # on-boundary keeps head
3733
- when :round then (head_next - parent).le(parent - head) # nearest, ties -> future
3776
+ when :ceil then face_storage.ne(head) # on-boundary keeps head
3777
+ when :round then (head_next - face_storage).le(face_storage - head) # nearest, ties -> future
3734
3778
  end
3735
3779
  (head + (head_next - head) * sel).time(unit: su)
3736
3780
  end
@@ -106,6 +106,14 @@ class CAWindowIterator < CAIterator
106
106
  "windows: expected #{@source.ndim} ranges (one per axis), " \
107
107
  "got #{@ranges.size}"
108
108
  end
109
+ # Each window is read through a view with one axis per anchor and one
110
+ # per cell in the window, twice the source's rank.
111
+ if 2 * @source.ndim > CA_RANK_MAX
112
+ raise ArgumentError,
113
+ "windows: a rank #{@source.ndim} array would need #{2 * @source.ndim} " \
114
+ "axes for its windows, beyond the limit of #{CA_RANK_MAX}; " \
115
+ "windows works up to rank #{CA_RANK_MAX / 2}"
116
+ end
109
117
  @ranges = @ranges.each_with_index.map { |r, k| window_offsets(r, k) }
110
118
 
111
119
  @sndim = @source.ndim
@@ -151,14 +159,15 @@ class CAWindowIterator < CAIterator
151
159
 
152
160
  # ---- padded entity + sliding_windows view -----------------------------
153
161
  #
154
- # The engine: pad the source once (:skip -> UNDEF, :nearest -> edge, :constant
162
+ # The engine: pad the source (:skip -> UNDEF, :nearest -> edge, :constant
155
163
  # -> value; :truncate does not pad), then take a sliding_windows view. Both
156
- # are memoised: every named reduction reuses the one pad + one view.
164
+ # are kept for one call, so the helpers a reduction runs share one pad and
165
+ # one view, and the next call pads the source as it is then.
157
166
 
158
167
  # @overload sliding_view
159
168
  # Returns the sliding_windows view feeding the reductions. For :truncate
160
169
  # this is the source's own view (zero-copy); otherwise it is the view over
161
- # the padded entity.
170
+ # a padded copy made from the source as it is at this call.
162
171
  # @return [CArray]
163
172
  def sliding_view
164
173
  @sliding_view ||= @shape.include?(0) ? no_windows : anchored_buffer.sliding_windows(*@widths)
@@ -553,7 +562,7 @@ class CAWindowIterator < CAIterator
553
562
  # Runs the fold by offset, or returns nil when this window is not one it can
554
563
  # answer for -- in which case the caller delegates as before.
555
564
  def fold_by_offset (op, min_count, fill_value = nil)
556
- return nil unless CArray.__fast_paths_enabled__
565
+ return nil unless CArray.__fast_paths_enabled__(:window_fold_by_offset)
557
566
  need = CArray.__present_cells_needed_per_result_cell__(op, min_count, 0)
558
567
  return nil unless op == :mean || OFFSET_FOLD.key?(op)
559
568
  # The elementwise kernels act on a Face's storage; its reductions are its own.
@@ -1150,6 +1159,9 @@ class CAWindowIterator < CAIterator
1150
1159
  yield idx
1151
1160
  end
1152
1161
  end
1162
+
1163
+ read_source_per_call :@sliding_view, :@padded_entity, :@neutral_value,
1164
+ :@offset_fold_base, :@window_cell_counts
1153
1165
  end
1154
1166
 
1155
1167
 
@@ -244,7 +244,8 @@ class CScalar
244
244
  # @!group Construction
245
245
 
246
246
  # @overload initialize(data_type)
247
- # Allocates a CScalar (0-D CArray) of the given `data_type`.
247
+ # Allocates a CScalar of the given `data_type`: one cell on one axis
248
+ # (`ndim` 1, `shape` `[1]`).
248
249
  # @param data_type [Symbol, Integer, Class, String]
249
250
  # @return [self]
250
251
  # @overload initialize(data_type, bytes:)
@@ -269,6 +270,30 @@ class CScalar
269
270
 
270
271
  # @!endgroup
271
272
 
273
+ # @!group Indexing and slicing
274
+
275
+ # @overload []
276
+ # @overload [](0)
277
+ # Reads the scalar's value. A CScalar is one cell and takes no index:
278
+ # `s[]` and `s[0]` both name that cell, and any other index (another
279
+ # integer, a range, `nil`, an index array, a symbol) is refused. A view
280
+ # of the scalar, such as `s.refer(s.data_type, [1])`, takes the usual
281
+ # indexes.
282
+ # @return [Object] the value, or `UNDEF` if it is masked.
283
+ # @raise [ArgumentError] when any other index is given.
284
+ def [](*index); end
285
+
286
+ # @overload []=(value)
287
+ # @overload []=(0, value)
288
+ # Writes the scalar's value. `s[] = v` and `s[0] = v` both write it;
289
+ # any other index is refused.
290
+ # @param value [Object] the value, or `UNDEF` to mask the cell.
291
+ # @return [Object] value
292
+ # @raise [ArgumentError] when any other index is given.
293
+ def []=(*index_and_value); end
294
+
295
+ # @!endgroup
296
+
272
297
  class << self
273
298
  # @!group Construction (typed shorthands)
274
299
 
@@ -23,14 +23,15 @@ class CArray
23
23
  # @return [CATranspose]
24
24
  # @overload transpose(*imap)
25
25
  # Returns a {CATranspose} view of `self` permuted by `imap`, a
26
- # permutation of `0 ... self.ndim`. Element
27
- # `view[i_0, ..., i_{n-1}]` aliases parent element with axis `k`
28
- # sourced from `i` at position `imap[k]`.
26
+ # permutation of `0 ... self.ndim`; a negative axis counts from the
27
+ # end. Element `view[i_0, ..., i_{n-1}]` aliases parent element with
28
+ # axis `k` sourced from `i` at position `imap[k]`.
29
29
  # @param imap [Array<Integer>] permutation of `0 ... ndim`.
30
30
  # @return [CATranspose]
31
31
  # @raise [ArgumentError] when `imap.length != ndim`.
32
32
  # @raise [ArgumentError] when an entry of `imap` is out of range
33
- # or duplicated.
33
+ # or names an axis given before.
34
+ # @raise [TypeError] when an entry of `imap` is not an Integer.
34
35
  def transpose(*imap); end
35
36
 
36
37
  # Alias of {#transpose}.
@@ -103,7 +103,9 @@ class CArray
103
103
 
104
104
  # @overload parent
105
105
  # Returns the parent CArray of `self`, or `nil` if `self` has no
106
- # parent (i.e. is an entity).
106
+ # parent (i.e. is an entity). For a Face (CATime, CARecord, ...) it
107
+ # is a read-only view of the storage; a Face whose storage may be
108
+ # written names a method for it (`CATime#ticks`).
107
109
  # @return [CArray, nil]
108
110
  def parent; end
109
111