carray 3.0.4 → 3.0.5

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 (111) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +205 -0
  3. data/carray.gemspec +1 -1
  4. data/ext/ca_array_pool.c +25 -1
  5. data/ext/ca_axis_dispatch.c +13 -20
  6. data/ext/ca_categorical_iterator.c +27 -20
  7. data/ext/ca_compare.c +4 -4
  8. data/ext/ca_composite_dispatch.c +9 -9
  9. data/ext/ca_kernel_iterator.c +6 -6
  10. data/ext/ca_kernel_iterator.h +112 -83
  11. data/ext/ca_obj_array.c +69 -19
  12. data/ext/ca_obj_bincmp.c +1 -0
  13. data/ext/ca_obj_binop.c +1 -0
  14. data/ext/ca_obj_bitarray.c +5 -1
  15. data/ext/ca_obj_bitfield.c +58 -43
  16. data/ext/ca_obj_byte_swap.c +2 -1
  17. data/ext/ca_obj_const_string.c +15 -3
  18. data/ext/ca_obj_face.c +42 -1
  19. data/ext/ca_obj_field.c +13 -3
  20. data/ext/ca_obj_grid.c +52 -5
  21. data/ext/ca_obj_monop.c +46 -10
  22. data/ext/ca_obj_object.c +64 -12
  23. data/ext/ca_obj_record.c +3 -0
  24. data/ext/ca_obj_reduce.c +4 -0
  25. data/ext/ca_obj_refer.c +18 -9
  26. data/ext/ca_obj_roll.c +26 -6
  27. data/ext/ca_obj_select.c +34 -5
  28. data/ext/ca_obj_shift.c +31 -7
  29. data/ext/ca_obj_stack.c +1 -0
  30. data/ext/ca_obj_stride.c +28 -19
  31. data/ext/ca_obj_string.c +11 -2
  32. data/ext/ca_obj_tile.c +27 -7
  33. data/ext/ca_obj_transpose.c +8 -4
  34. data/ext/ca_obj_triop.c +3 -0
  35. data/ext/ca_obj_window.c +79 -50
  36. data/ext/ca_op_ipower.c +4 -4
  37. data/ext/ca_sort_kernels.h +6 -3
  38. data/ext/carray.h +33 -0
  39. data/ext/carray_access.c +16 -3
  40. data/ext/carray_attribute.c +24 -0
  41. data/ext/carray_cast.c +243 -82
  42. data/ext/carray_cast_func.rb +56 -26
  43. data/ext/carray_conversion.c +2 -2
  44. data/ext/carray_core.c +3 -0
  45. data/ext/carray_count.c +6 -0
  46. data/ext/carray_element.c +9 -4
  47. data/ext/carray_factorize.c +20 -20
  48. data/ext/carray_generate.c +8 -2
  49. data/ext/carray_histogram.c +1 -1
  50. data/ext/carray_index_classifier.c +4 -4
  51. data/ext/carray_internal.h +12 -0
  52. data/ext/carray_kernels_bincmp.c +168 -168
  53. data/ext/carray_kernels_binop.c +900 -900
  54. data/ext/carray_kernels_init.c +4 -4
  55. data/ext/carray_kernels_moncmp.c +96 -96
  56. data/ext/carray_kernels_monop.c +1984 -1984
  57. data/ext/carray_kernels_reduce_aggregate.c +1733 -1733
  58. data/ext/carray_kernels_reduce_cumulative.c +642 -642
  59. data/ext/carray_kernels_reduce_extreme.c +1150 -1150
  60. data/ext/carray_kernels_reduce_variance.c +258 -258
  61. data/ext/carray_kernels_scan.c +128 -128
  62. data/ext/carray_kernels_search.c +340 -340
  63. data/ext/carray_kernels_sort.c +295 -304
  64. data/ext/carray_kernels_triop.c +114 -114
  65. data/ext/carray_key_windows.c +4 -0
  66. data/ext/carray_mask.c +45 -22
  67. data/ext/carray_memory_view.c +2 -2
  68. data/ext/carray_operator.c +5 -5
  69. data/ext/carray_order.c +227 -8
  70. data/ext/carray_random.c +18 -18
  71. data/ext/carray_scatter.c +24 -7
  72. data/ext/carray_slab.c +32 -9
  73. data/ext/carray_sort.c +224 -16
  74. data/ext/carray_sort_kernel.c +5 -7
  75. data/ext/carray_struct.c +35 -43
  76. data/ext/carray_time_text.c +4 -2
  77. data/ext/carray_utils.c +63 -21
  78. data/ext/mkkernel.rb +210 -115
  79. data/ext/version.h +4 -4
  80. data/lib/carray/attribute.rb +17 -4
  81. data/lib/carray/basics.rb +27 -7
  82. data/lib/carray/categorical.rb +30 -16
  83. data/lib/carray/categorical_iterator.rb +6 -6
  84. data/lib/carray/const_string.rb +10 -0
  85. data/lib/carray/data_type_extension.rb +8 -1
  86. data/lib/carray/fixlen_string.rb +41 -2
  87. data/lib/carray/frame/column_verbs.rb +4 -3
  88. data/lib/carray/frame/concat.rb +17 -3
  89. data/lib/carray/frame/frame.rb +6 -7
  90. data/lib/carray/frame/group.rb +100 -13
  91. data/lib/carray/frame/io.rb +3 -1
  92. data/lib/carray/fuse_source.rb +31 -2
  93. data/lib/carray/lazy.rb +35 -0
  94. data/lib/carray/methods/is_in.rb +8 -1
  95. data/lib/carray/segment_iterator.rb +12 -8
  96. data/lib/carray/serialize.rb +19 -2
  97. data/lib/carray/stack.rb +15 -12
  98. data/lib/carray/string.rb +8 -0
  99. data/lib/carray/string_operation_extension.rb +13 -5
  100. data/lib/carray/struct_builder.rb +5 -3
  101. data/lib/carray/time.rb +107 -68
  102. data/yard-stubs/ca_obj_transpose.rb +5 -4
  103. data/yard-stubs/carray_attribute.rb +3 -1
  104. data/yard-stubs/carray_cast.rb +2 -0
  105. data/yard-stubs/carray_conversion.rb +5 -1
  106. data/yard-stubs/carray_element.rb +7 -6
  107. data/yard-stubs/carray_mask.rb +8 -3
  108. data/yard-stubs/carray_math.rb +14 -4
  109. data/yard-stubs/carray_order.rb +100 -4
  110. data/yard-stubs/carray_scatter.rb +7 -5
  111. metadata +1 -1
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,7 @@ 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
+ CATimeUnitAlgebra.convert_instant_floor(face_storage, self.unit, to).time(unit: to)
1105
1126
  end
1106
1127
 
1107
1128
  # @overload +(other)
@@ -1116,7 +1137,9 @@ class CATime
1116
1137
  # into whole ticks; on any other (`:W`, `"3 days"`) a month raises, as a
1117
1138
  # fixed duration on a month or year grid does. Adding two datetimes is
1118
1139
  # ill-defined. A single duration -- a {CATimedelta::Element} or a String
1119
- # such as `"3 days"` -- is added to every cell.
1140
+ # such as `"3 days"` -- is added to every cell; a String is one step of
1141
+ # its own resolution, so `"15 minutes"` on a `"10 minutes"` grid raises
1142
+ # as `CArray.int64(1).timedelta(unit: "15 minutes")` does.
1120
1143
  # @param other [CATimedelta, CATimedelta::Element, String]
1121
1144
  # (or a MemoryView producer whose +to_ca+ gives one, such as an Arrow
1122
1145
  # duration array)
@@ -1126,17 +1149,18 @@ class CATime
1126
1149
  # @raise [RangeError] when a result leaves the range of int64 ticks.
1127
1150
  def +(other)
1128
1151
  given, other = other, CATimeUnitAlgebra.operand(other)
1129
- other = CATimeUnitAlgebra.scalar_operand(other, unit) || other
1152
+ other = CATimeUnitAlgebra.step_operand(other) ||
1153
+ CATimeUnitAlgebra.scalar_operand(other, unit) || other
1130
1154
  case other
1131
1155
  when CATimedelta
1132
1156
  return shifted_by_months(other, 1) if moved_by_months?(other)
1133
1157
  d = CATimeUnitAlgebra.duration_on_grid(other.parent, other.unit, unit)
1134
1158
  d = d[0] if other.scalar?
1135
- CATimeUnitAlgebra.checked_add(parent, d).time(unit: unit)
1159
+ CATimeUnitAlgebra.checked_add(face_storage, d).time(unit: unit)
1136
1160
  when CATime
1137
1161
  raise TypeError, "CATime + CATime is ill-defined"
1138
1162
  else
1139
- raise TypeError, "CATime + #{given.class} is not allowed: add a duration " \
1163
+ raise TypeError, "CATime + #{CATimeUnitAlgebra.operand_name(given)} is not allowed: add a duration " \
1140
1164
  "(a CATimedelta, a CATimedelta::Element or a String such as \"3 days\")"
1141
1165
  end
1142
1166
  end
@@ -1155,21 +1179,22 @@ class CATime
1155
1179
  # @raise [TypeError] on an unsupported operand.
1156
1180
  def -(other)
1157
1181
  given, other = other, CATimeUnitAlgebra.operand(other)
1158
- other = CATimeUnitAlgebra.scalar_operand(other, unit) || other
1182
+ other = CATimeUnitAlgebra.step_operand(other) ||
1183
+ CATimeUnitAlgebra.scalar_operand(other, unit) || other
1159
1184
  case other
1160
1185
  when CATimedelta
1161
1186
  return shifted_by_months(other, -1) if moved_by_months?(other)
1162
1187
  d = CATimeUnitAlgebra.duration_on_grid(other.parent, other.unit, unit)
1163
1188
  d = d[0] if other.scalar?
1164
- CATimeUnitAlgebra.checked_sub(parent, d).time(unit: unit)
1189
+ CATimeUnitAlgebra.checked_sub(face_storage, d).time(unit: unit)
1165
1190
  when CATime
1166
1191
  u = CATimeUnitAlgebra.diff_unit(unit, other.unit)
1167
- a = CATimeUnitAlgebra.convert_instant!(parent, unit, u)
1192
+ a = CATimeUnitAlgebra.convert_instant!(face_storage, unit, u)
1168
1193
  b = CATimeUnitAlgebra.convert_instant!(other.parent, other.unit, u)
1169
1194
  b = b[0] if other.scalar?
1170
1195
  CATimeUnitAlgebra.checked_sub(a, b).timedelta(unit: u)
1171
1196
  else
1172
- raise TypeError, "CATime - #{given.class} is not allowed: subtract a duration " \
1197
+ raise TypeError, "CATime - #{CATimeUnitAlgebra.operand_name(given)} is not allowed: subtract a duration " \
1173
1198
  "(a CATimedelta, a CATimedelta::Element or a String such as \"3 days\") " \
1174
1199
  "or a time (a CATime, a CATime::Element, a Time, a Date or a String)"
1175
1200
  end
@@ -1189,7 +1214,7 @@ class CATime
1189
1214
  private def shifted_by_months (td, sign)
1190
1215
  months = td.parent.int64 * (sign * CATimeUnitAlgebra.months_per_tick(td.unit))
1191
1216
  months = months[0] if td.scalar?
1192
- CATimeUnitAlgebra.shift_months(parent, unit, months).time(unit: unit)
1217
+ CATimeUnitAlgebra.shift_months(face_storage, unit, months).time(unit: unit)
1193
1218
  end
1194
1219
 
1195
1220
  # @overload pmax(other)
@@ -1255,8 +1280,9 @@ class CATime
1255
1280
  # they run on the storage ticks and round the result back to the nearest
1256
1281
  # tick of `unit`. A caller who needs a finer answer declares the grid
1257
1282
  # 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.
1283
+ # Rounding is to nearest, and a tie goes to the later tick: rounding half
1284
+ # away from zero would send a tie later after 1970 and earlier before it,
1285
+ # so moving the data by whole days would change the answer.
1260
1286
 
1261
1287
  # @overload mean(axis: nil, **opts)
1262
1288
  # Returns the centroid time on `self`'s unit (§8), rounded to the
@@ -1492,7 +1518,7 @@ class CATime
1492
1518
  # @return [Element, CATime] an {Element} for a scalar `addr`, `nil` when
1493
1519
  # that single answer is undetermined.
1494
1520
  def linear_fetch (addr, **opts)
1495
- r = parent.float64.linear_fetch(addr, **opts)
1521
+ r = face_storage.float64.linear_fetch(addr, **opts)
1496
1522
  case r
1497
1523
  when CArray
1498
1524
  # mask_invalid before the int64 cast: the cast turns a NaN into 0, which
@@ -1523,7 +1549,7 @@ class CATime
1523
1549
  def to_time
1524
1550
  if CATimeUnitAlgebra::FIXED.key?(unit.base)
1525
1551
  f = unit.tick_ratio # exact seconds / tick (Rational)
1526
- parent.convert(:object) {|v| Time.at(v * f, in: 'UTC')}
1552
+ face_storage.convert(:object) {|v| Time.at(v * f, in: 'UTC')}
1527
1553
  else # calendar: exact granule midnight
1528
1554
  (days_since_epoch * 86400).convert(:object) {|v| Time.at(v, in: 'UTC')}
1529
1555
  end
@@ -1557,8 +1583,8 @@ class CATime
1557
1583
  # @return [CArray]
1558
1584
  def year
1559
1585
  case unit.base
1560
- when :Y then parent * unit.count + 1970
1561
- when :M then (parent * unit.count + 1970 * 12) / 12
1586
+ when :Y then face_storage * unit.count + 1970
1587
+ when :M then (face_storage * unit.count + 1970 * 12) / 12
1562
1588
  else CATimeCivil.civil_from_days(days_since_epoch)[0]
1563
1589
  end
1564
1590
  end
@@ -1568,9 +1594,9 @@ class CATime
1568
1594
  # @return [CArray] integer.
1569
1595
  def month
1570
1596
  case unit.base
1571
- when :Y then parent * 0 + 1
1597
+ when :Y then face_storage * 0 + 1
1572
1598
  when :M
1573
- mo = parent * unit.count + 1970 * 12
1599
+ mo = face_storage * unit.count + 1970 * 12
1574
1600
  mo % 12 + 1
1575
1601
  else CATimeCivil.civil_from_days(days_since_epoch)[1]
1576
1602
  end
@@ -1581,7 +1607,7 @@ class CATime
1581
1607
  # @return [CArray] integer.
1582
1608
  def day
1583
1609
  case unit.base
1584
- when :Y, :M then parent * 0 + 1
1610
+ when :Y, :M then face_storage * 0 + 1
1585
1611
  else CATimeCivil.civil_from_days(days_since_epoch)[2]
1586
1612
  end
1587
1613
  end
@@ -1704,7 +1730,7 @@ class CATime
1704
1730
  # writes to_unit(:D) first) -- and the result is rounded back to a tick.
1705
1731
  def reduce_on_own_grid(op, kind, args, opts)
1706
1732
  with_surface_fill(opts) do |o|
1707
- round_and_relift(parent.public_send(op, *args, **o), kind)
1733
+ round_and_relift(face_storage.public_send(op, *args, **o), kind)
1708
1734
  end
1709
1735
  end
1710
1736
 
@@ -1714,14 +1740,15 @@ class CATime
1714
1740
  def round_and_relift(r, kind)
1715
1741
  case r
1716
1742
  when Numeric
1717
- v = r.round
1743
+ v = kind == :timedelta ? r.round : (r + 0.5).floor
1718
1744
  kind == :timedelta ? CATimedelta::Element.new(v, unit) : Element.new(v, unit)
1719
1745
  when CArray
1720
1746
  # A cell with no answer arrives masked and the mask survives the cast.
1721
1747
  # NaN does not: it has no int64 image, so a `fill_value: Float::NAN`
1722
1748
  # cell would cast to garbage bits. mask_invalid turns it into UNDEF,
1723
1749
  # which is what "no answer" means here.
1724
- iv = r.round.mask_invalid.int64
1750
+ near = kind == :timedelta ? r.round : (r + 0.5).floor
1751
+ iv = near.mask_invalid.int64
1725
1752
  kind == :timedelta ? iv.timedelta(unit: unit) : iv.time(unit: unit)
1726
1753
  when Array
1727
1754
  r.map {|x| round_and_relift(x, kind)}
@@ -1781,7 +1808,7 @@ class CATimedelta
1781
1808
  # measured in this array's resolution (see {CATime#ticks}).
1782
1809
  # @return [CArray]
1783
1810
  def ticks
1784
- parent
1811
+ face_storage
1785
1812
  end
1786
1813
 
1787
1814
  # @overload to_unit(unit)
@@ -1803,7 +1830,7 @@ class CATimedelta
1803
1830
  # @raise [RangeError] when the widened ticks overflow int64.
1804
1831
  def to_unit(unit)
1805
1832
  to = CATime::Resolution.parse(unit)
1806
- CATimeUnitAlgebra.convert_scale_trunc(parent, self.unit, to).timedelta(unit: to)
1833
+ CATimeUnitAlgebra.convert_scale_trunc(face_storage, self.unit, to).timedelta(unit: to)
1807
1834
  end
1808
1835
 
1809
1836
  # @overload +(other)
@@ -1821,16 +1848,16 @@ class CATimedelta
1821
1848
  case other
1822
1849
  when CATimedelta
1823
1850
  u = common_duration_unit(other.unit)
1824
- a = CATimeUnitAlgebra.convert_scale!(parent, unit, u)
1851
+ a = CATimeUnitAlgebra.convert_scale!(face_storage, unit, u)
1825
1852
  b = CATimeUnitAlgebra.convert_scale!(other.parent, other.unit, u)
1826
1853
  b = b[0] if other.scalar?
1827
1854
  CATimeUnitAlgebra.checked_add(a, b).timedelta(unit: u)
1828
1855
  when CATime
1829
1856
  return other + self unless other.scalar? # commutative -> time's unit
1830
- d = CATimeUnitAlgebra.duration_on_grid(parent, unit, other.unit)
1857
+ d = CATimeUnitAlgebra.duration_on_grid(face_storage, unit, other.unit)
1831
1858
  CATimeUnitAlgebra.checked_add(d, other.parent[0]).time(unit: other.unit)
1832
1859
  else
1833
- raise TypeError, "CATimedelta + #{given.class} is not allowed"
1860
+ raise TypeError, "CATimedelta + #{CATimeUnitAlgebra.operand_name(given)} is not allowed"
1834
1861
  end
1835
1862
  end
1836
1863
 
@@ -1848,12 +1875,12 @@ class CATimedelta
1848
1875
  case other
1849
1876
  when CATimedelta
1850
1877
  u = common_duration_unit(other.unit)
1851
- a = CATimeUnitAlgebra.convert_scale!(parent, unit, u)
1878
+ a = CATimeUnitAlgebra.convert_scale!(face_storage, unit, u)
1852
1879
  b = CATimeUnitAlgebra.convert_scale!(other.parent, other.unit, u)
1853
1880
  b = b[0] if other.scalar?
1854
1881
  CATimeUnitAlgebra.checked_sub(a, b).timedelta(unit: u)
1855
1882
  else
1856
- raise TypeError, "CATimedelta - #{given.class} is not allowed"
1883
+ raise TypeError, "CATimedelta - #{CATimeUnitAlgebra.operand_name(given)} is not allowed"
1857
1884
  end
1858
1885
  end
1859
1886
 
@@ -1862,14 +1889,14 @@ class CATimedelta
1862
1889
  # unit.
1863
1890
  # @return [CATimedelta]
1864
1891
  def abs
1865
- parent.abs.timedelta(unit: unit)
1892
+ face_storage.abs.timedelta(unit: unit)
1866
1893
  end
1867
1894
 
1868
1895
  # @overload -@
1869
1896
  # Returns each duration with its sign reversed, on the same unit.
1870
1897
  # @return [CATimedelta]
1871
1898
  def -@
1872
- (-parent).timedelta(unit: unit)
1899
+ (-face_storage).timedelta(unit: unit)
1873
1900
  end
1874
1901
 
1875
1902
  # @overload *(other)
@@ -1879,8 +1906,8 @@ class CATimedelta
1879
1906
  # @raise [TypeError] when `other` is not an Integer.
1880
1907
  def *(other)
1881
1908
  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)"
1909
+ when Integer then CATimeUnitAlgebra.checked_mul(face_storage, other).timedelta(unit: unit)
1910
+ else raise TypeError, "CATimedelta * #{CATimeUnitAlgebra.operand_name(other)} is not allowed (Integer only)"
1884
1911
  end
1885
1912
  end
1886
1913
 
@@ -1898,13 +1925,13 @@ class CATimedelta
1898
1925
  given, other = other, CATimeUnitAlgebra.operand(other)
1899
1926
  case other
1900
1927
  when Integer
1901
- CATimeUnitAlgebra.trunc_divide(parent, other).timedelta(unit: unit)
1928
+ CATimeUnitAlgebra.trunc_divide(face_storage, other).timedelta(unit: unit)
1902
1929
  when CATimedelta
1903
1930
  # A ratio of durations is a number, not a count of whole ticks: 90 s /
1904
1931
  # 60 s is 1.5. Both are brought to one unit first, so the ratio does
1905
1932
  # not depend on the units they are held in.
1906
1933
  u = common_duration_unit(other.unit)
1907
- CATimeUnitAlgebra.convert_scale!(parent, unit, u).to_type(:float64) /
1934
+ CATimeUnitAlgebra.convert_scale!(face_storage, unit, u).to_type(:float64) /
1908
1935
  CATimeUnitAlgebra.convert_scale!(other.parent, other.unit, u)
1909
1936
  else raise TypeError, "CATimedelta / #{given.class} is not allowed"
1910
1937
  end
@@ -1926,7 +1953,7 @@ class CATimedelta
1926
1953
  # reduction, if any of its cells does not).
1927
1954
  def sum(*args, **opts)
1928
1955
  with_surface_fill(opts) { |o|
1929
- exact_sum_relift(parent.accumulate(*args, **o), parent.sum(*args, **o))
1956
+ exact_sum_relift(face_storage.accumulate(*args, **o), face_storage.sum(*args, **o))
1930
1957
  }
1931
1958
  end
1932
1959
 
@@ -1955,7 +1982,7 @@ class CATimedelta
1955
1982
  # the same answer when it fits and raises RangeError when it does not.
1956
1983
  # @return [Element, CATimedelta]
1957
1984
  def accumulate(*args, **opts)
1958
- with_surface_fill(opts) { |o| round_and_relift(parent.accumulate(*args, **o)) }
1985
+ with_surface_fill(opts) { |o| round_and_relift(face_storage.accumulate(*args, **o)) }
1959
1986
  end
1960
1987
 
1961
1988
  # @overload cumsum(axis: nil)
@@ -1966,8 +1993,8 @@ class CATimedelta
1966
1993
  # @return [CATimedelta]
1967
1994
  # @raise [RangeError] if any cell of the running sum does not fit int64.
1968
1995
  def cumsum(*args, **opts)
1969
- exact_sum_relift(parent.send(:cumsum_in_own_type, *args, **opts),
1970
- parent.cumsum(*args, **opts))
1996
+ exact_sum_relift(face_storage.send(:cumsum_in_own_type, *args, **opts),
1997
+ face_storage.cumsum(*args, **opts))
1971
1998
  end
1972
1999
 
1973
2000
  # @overload prod(*)
@@ -1985,7 +2012,7 @@ class CATimedelta
1985
2012
  # Returns the mean duration rounded to the nearest unit count.
1986
2013
  # @return [Element, CATimedelta]
1987
2014
  def mean(*args, **opts)
1988
- with_surface_fill(opts) { |o| round_and_relift(parent.mean(*args, **o)) }
2015
+ with_surface_fill(opts) { |o| round_and_relift(face_storage.mean(*args, **o)) }
1989
2016
  end
1990
2017
 
1991
2018
  # median / percentile / quantile / stddev / stddevp report on this array's
@@ -1997,7 +2024,7 @@ class CATimedelta
1997
2024
  # Returns the median duration on `self`'s unit.
1998
2025
  # @return [Element, CATimedelta]
1999
2026
  def median(*args, **opts)
2000
- with_surface_fill(opts) { |o| round_and_relift(parent.median(*args, **o)) }
2027
+ with_surface_fill(opts) { |o| round_and_relift(face_storage.median(*args, **o)) }
2001
2028
  end
2002
2029
 
2003
2030
  # @overload percentile(*p, axis: nil, **opts)
@@ -2006,7 +2033,7 @@ class CATimedelta
2006
2033
  # two or more give an Array).
2007
2034
  # @return [Element, CATimedelta, Array<Element>, Array<CATimedelta>]
2008
2035
  def percentile(*args, **opts)
2009
- with_surface_fill(opts) { |o| round_and_relift(parent.percentile(*args, **o)) }
2036
+ with_surface_fill(opts) { |o| round_and_relift(face_storage.percentile(*args, **o)) }
2010
2037
  end
2011
2038
 
2012
2039
  # @overload quantile(axis: nil, **opts)
@@ -2014,7 +2041,7 @@ class CATimedelta
2014
2041
  # `self`'s unit.
2015
2042
  # @return [Array<Element>, Array<CATimedelta>]
2016
2043
  def quantile(*args, **opts)
2017
- with_surface_fill(opts) { |o| round_and_relift(parent.quantile(*args, **o)) }
2044
+ with_surface_fill(opts) { |o| round_and_relift(face_storage.quantile(*args, **o)) }
2018
2045
  end
2019
2046
 
2020
2047
  # @overload stddev(axis: nil, **opts)
@@ -2023,7 +2050,7 @@ class CATimedelta
2023
2050
  # the precision matters.
2024
2051
  # @return [Element, CATimedelta]
2025
2052
  def stddev(*args, **opts)
2026
- with_surface_fill(opts) { |o| round_and_relift(parent.stddev(*args, **o)) }
2053
+ with_surface_fill(opts) { |o| round_and_relift(face_storage.stddev(*args, **o)) }
2027
2054
  end
2028
2055
 
2029
2056
  # @overload stddevp(axis: nil, **opts)
@@ -2031,7 +2058,7 @@ class CATimedelta
2031
2058
  # {#stddev}.
2032
2059
  # @return [Element, CATimedelta]
2033
2060
  def stddevp(*args, **opts)
2034
- with_surface_fill(opts) { |o| round_and_relift(parent.stddevp(*args, **o)) }
2061
+ with_surface_fill(opts) { |o| round_and_relift(face_storage.stddevp(*args, **o)) }
2035
2062
  end
2036
2063
 
2037
2064
  def round_and_relift(r)
@@ -2168,7 +2195,7 @@ class CATimedelta
2168
2195
  # @return [Element, CATimedelta] an {Element} for a scalar `addr`, `nil`
2169
2196
  # when that single answer is undetermined.
2170
2197
  def linear_fetch (addr, **opts)
2171
- r = parent.float64.linear_fetch(addr, **opts)
2198
+ r = face_storage.float64.linear_fetch(addr, **opts)
2172
2199
  case r
2173
2200
  when CArray then r.mask_invalid.round.int64.timedelta(unit: unit)
2174
2201
  when Numeric then r.to_f.nan? ? nil : Element.new(r.round, unit)
@@ -3464,8 +3491,18 @@ class CATime
3464
3491
  fl = o + q * step_ticks
3465
3492
  return bucket_head_as_time(fl + step_ticks * n.ne(fl), mul, su)
3466
3493
  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
3494
+ if mul == 1 and [:M, :Y].include?(su.base)
3495
+ # A month or year tick is the start of its month or year, so the
3496
+ # nearest head is by the instant, measured in days.
3497
+ fl = o + q * step_ticks
3498
+ here = days_since_epoch
3499
+ lo = fl.time(unit: su).send(:days_since_epoch)
3500
+ hi = (fl + step_ticks).time(unit: su).send(:days_since_epoch)
3501
+ q = q + (hi - here).le(here - lo) # ties -> future
3502
+ else
3503
+ r = d - q * step_ticks # 0 <= r < step_ticks
3504
+ q = q + r.ge((step_ticks + 1) / 2) # ties -> future; no 2*d / 2*r
3505
+ end
3469
3506
  end
3470
3507
  bucket_head_as_time(o + q * step_ticks, mul, su)
3471
3508
  end
@@ -3517,8 +3554,10 @@ class CATime
3517
3554
  # @overload round(grid = nil, unit:, origin: nil)
3518
3555
  # {#snap} with `direction: :round` -- each element at its nearest bucket
3519
3556
  # 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.
3557
+ # loss). For a calendar bucket the nearest head is by the instant (month
3558
+ # lengths vary), ties toward the future. A month or year storage tick
3559
+ # stands for the start of its month or year, so a `:M` array rounds as
3560
+ # the same dates on `:D` do: 1889-07 to the year is 1889.
3522
3561
  # @return [CATime]
3523
3562
  def round(grid = nil, unit: nil, origin: nil)
3524
3563
  snap(grid, unit: unit, origin: origin, direction: :round)
@@ -3604,7 +3643,7 @@ class CATime
3604
3643
 
3605
3644
  # Storage ticks lifted into the numerator grid (see {#resolve_timestep_grid}).
3606
3645
  def storage_ticks_in_numerator_grid(mul)
3607
- mul == 1 ? parent : CATimeUnitAlgebra.widen(parent, mul)
3646
+ mul == 1 ? face_storage : CATimeUnitAlgebra.widen(face_storage, mul)
3608
3647
  end
3609
3648
 
3610
3649
  # A bucket head computed in numerator ticks, back as a {CATime} in the
@@ -3638,15 +3677,15 @@ class CATime
3638
3677
  # an empty array, or one whose every cell is masked.
3639
3678
  def raise_if_int64_overflow(su, mul, o, step_ticks = nil, kind = nil)
3640
3679
  return unless mul > 1 or FINE_UNITS.include?(su.base)
3641
- min = parent.min
3680
+ min = face_storage.min
3642
3681
  return if min == UNDEF
3643
3682
  lo = Integer(min) * mul
3644
- hi = Integer(parent.max) * mul
3683
+ hi = Integer(face_storage.max) * mul
3645
3684
  chk = ->(v, w) { CATimeGrid.check_int64_range(v, w) }
3646
3685
  chk.(lo, "ticks in bucket resolution")
3647
3686
  chk.(hi, "ticks in bucket resolution")
3648
- chk.(lo - o, "parent - origin")
3649
- chk.(hi - o, "parent - origin")
3687
+ chk.(lo - o, "face_storage - origin")
3688
+ chk.(hi - o, "face_storage - origin")
3650
3689
  if kind
3651
3690
  chk.(bucket_head_ticks(lo, o, step_ticks, kind), "bucket head")
3652
3691
  chk.(bucket_head_ticks(hi, o, step_ticks, kind), "bucket head")
@@ -3674,22 +3713,22 @@ class CATime
3674
3713
  case res.base
3675
3714
  when :Y
3676
3715
  ones = CArray.int64(*shape) { 1 }
3677
- CATimeCivil.days_from_civil(parent * res.count + 1970, ones, ones)
3716
+ CATimeCivil.days_from_civil(face_storage * res.count + 1970, ones, ones)
3678
3717
  when :M
3679
- mo = parent * res.count + 1970 * 12
3718
+ mo = face_storage * res.count + 1970 * 12
3680
3719
  y = mo / 12
3681
3720
  m = mo - y * 12 + 1
3682
3721
  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
3722
+ when :W then face_storage * res.count * 7
3723
+ when :D then face_storage * res.count
3685
3724
  else # fixed sub-day storage
3686
3725
  day = CATime::Resolution.new(1, :D)
3687
3726
  r = CATimeUnitAlgebra.ratio(day, res) # storage ticks per day
3688
3727
  if r.denominator == 1
3689
- parent / r.numerator
3728
+ face_storage / r.numerator
3690
3729
  else # non-day-aligned tick
3691
3730
  tr = res.tick_ratio # seconds / tick
3692
- parent * tr.numerator / (86400 * tr.denominator)
3731
+ face_storage * tr.numerator / (86400 * tr.denominator)
3693
3732
  end
3694
3733
  end
3695
3734
  end
@@ -3702,9 +3741,9 @@ class CATime
3702
3741
  # calendar-storage element has no intra-day field, so it collapses to 0.
3703
3742
  def clock_field(fu)
3704
3743
  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
3744
+ return face_storage * 0 if r.nil? # calendar storage: no clock field
3745
+ fi = r.denominator == 1 ? face_storage * r.numerator # epoch-relative fu index
3746
+ : face_storage * r.numerator / r.denominator
3708
3747
  cycle = fu == :h ? 24 : 60 # hour 0..23; min/sec 0..59
3709
3748
  fi % cycle
3710
3749
  end
@@ -3725,12 +3764,12 @@ class CATime
3725
3764
  return k if kind == :index
3726
3765
  head = CATimeGrid.calendar_bucket_head_ticks(k, st, su, origin)
3727
3766
  return head.time(unit: su) if kind == :floor
3728
- return parent.eq(head) if kind == :on
3767
+ return face_storage.eq(head) if kind == :on
3729
3768
  head_next = CATimeGrid.calendar_bucket_head_ticks(k + 1, st, su, origin)
3730
3769
  sel = # boolean 0/1: pick head_next?
3731
3770
  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
3771
+ when :ceil then face_storage.ne(head) # on-boundary keeps head
3772
+ when :round then (head_next - face_storage).le(face_storage - head) # nearest, ties -> future
3734
3773
  end
3735
3774
  (head + (head_next - head) * sel).time(unit: su)
3736
3775
  end
@@ -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
 
@@ -472,6 +472,8 @@ class CArray
472
472
  #
473
473
  # - CArray instance -> its `data_type`
474
474
  # - Symbol / String / Class -> data_type representation (name lookup)
475
+ # - MemoryView producer -> the data_type its format names; one whose
476
+ # format carray cannot read is refused, as an operator refuses it
475
477
  # - Integer / Float / Complex / true / false / nil / Object -> *value*,
476
478
  # data_type inferred (3 -> `:int64`, 3.14 -> `:float64`,
477
479
  # 1+2i -> `:cmplx128`, ...)
@@ -63,7 +63,11 @@ class CArray
63
63
 
64
64
  # @overload to_s
65
65
  # Equivalent to `dump_binary` with no arguments. Returns the raw
66
- # element bytes of `self` as a binary String.
66
+ # element bytes of `self` as a binary String. Ruby uses `to_s` where
67
+ # it puts an object into text, so such text holds the bytes: a
68
+ # `Ractor.make_shareable` refusal of a view, for one, raises
69
+ # `ArgumentError: string contains null byte` in place of its own
70
+ # message. Use `inspect` for a readable form.
67
71
  # @return [String]
68
72
  def to_s; end
69
73
 
@@ -5,12 +5,13 @@
5
5
  class CArray
6
6
  # @!group Element access
7
7
  #
8
- # `elem_*` methods are low-level single-cell operations. They
9
- # accept `idx` as either an Integer (flat address into the
10
- # row-major element sequence) or an `Array<Integer>` (one index
11
- # per axis). They bypass the broadcasting and view-construction
12
- # paths used by `[]` / `[]=`, and are the right tool for
13
- # tight per-cell loops where allocation matters.
8
+ # `elem_*` methods are the fastest way to reach one cell when you
9
+ # hold its per-axis index. They accept `idx` as an `Array<Integer>`
10
+ # (one index per axis) or an Integer (flat address into the
11
+ # row-major element sequence). An index is read as `[]` reads one:
12
+ # an Integer, or a Float with no fraction. The result is the same as `[]` /
13
+ # `[]=` for that cell; the key parsing and view construction of
14
+ # the general indexer are skipped.
14
15
 
15
16
  # @overload elem_swap(idx1, idx2)
16
17
  # Exchanges the values (and mask states, if any) at positions
@@ -30,6 +30,10 @@ class CArray
30
30
  # Nothing in a value array is masked, so `unmask` on it does nothing,
31
31
  # while a write that would mask a cell (`UNDEF`, `mask=`,
32
32
  # `invert_mask`) raises TypeError.
33
+ #
34
+ # On a Face the data slot is read through the Face. A {CACategorical}
35
+ # cell excluded by the sentinel code has no label, so its value reads
36
+ # as `nil` (the codes are in `codes`).
33
37
  # @return [CArray]
34
38
  def value; end
35
39
 
@@ -180,10 +184,11 @@ class CArray
180
184
  # of `self` and the masks of each array in `others`. Mutates
181
185
  # `self`.
182
186
  # @param others [Array<CArray>] arrays whose mask states are
183
- # OR-ed into `self`. Non-CArray arguments are ignored.
187
+ # OR-ed into `self`.
184
188
  # @return [self]
185
189
  # @raise [ArgumentError] if an array in `others` other than a scalar
186
- # has fewer elements than `self`.
190
+ # has a different number of elements from `self`.
191
+ # @raise [TypeError] if an argument is not a CArray.
187
192
  def inherit_mask(*others); end
188
193
 
189
194
  # @overload inherit_mask_replace(*others)
@@ -194,7 +199,7 @@ class CArray
194
199
  # new mask of `self`.
195
200
  # @return [self]
196
201
  # @raise [ArgumentError] if an array in `others` other than a scalar
197
- # has fewer elements than `self`.
202
+ # has a different number of elements from `self`.
198
203
  def inherit_mask_replace(*others); end
199
204
 
200
205
  # @overload count_masked