carray 3.0.6 → 3.0.7

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 (139) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +110 -0
  3. data/README.md +24 -3
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_group.c +6 -6
  6. data/ext/ca_binop_dispatch.c +8 -0
  7. data/ext/ca_binop_dispatch.h +1 -0
  8. data/ext/ca_categorical_iterator.c +7 -7
  9. data/ext/ca_kernel_iterator.h +6 -4
  10. data/ext/ca_obj_array.c +4 -4
  11. data/ext/ca_obj_bincmp.c +19 -3
  12. data/ext/ca_obj_binop.c +36 -11
  13. data/ext/ca_obj_bitarray.c +2 -4
  14. data/ext/ca_obj_bitfield.c +86 -4
  15. data/ext/ca_obj_byte_swap.c +2 -4
  16. data/ext/ca_obj_const_string.c +422 -257
  17. data/ext/ca_obj_face.c +71 -33
  18. data/ext/ca_obj_fake.c +112 -13
  19. data/ext/ca_obj_farray.c +7 -1
  20. data/ext/ca_obj_fixlen_string.c +51 -17
  21. data/ext/ca_obj_grid.c +12 -7
  22. data/ext/ca_obj_meld.c +5 -5
  23. data/ext/ca_obj_monop.c +65 -39
  24. data/ext/ca_obj_object.c +9 -9
  25. data/ext/ca_obj_reduce.c +2 -4
  26. data/ext/ca_obj_refer.c +104 -10
  27. data/ext/ca_obj_repeat.c +1 -1
  28. data/ext/ca_obj_roll.c +2 -4
  29. data/ext/ca_obj_select.c +8 -6
  30. data/ext/ca_obj_select_axis.c +8 -6
  31. data/ext/ca_obj_stack.c +5 -5
  32. data/ext/ca_obj_stride.c +46 -8
  33. data/ext/ca_obj_string.c +2 -2
  34. data/ext/ca_obj_tile.c +2 -4
  35. data/ext/ca_obj_triop.c +15 -12
  36. data/ext/ca_obj_window.c +22 -15
  37. data/ext/ca_op_ipower.c +63 -15
  38. data/ext/ca_op_powi.h +6 -29
  39. data/ext/ca_sort_kernels.h +3 -0
  40. data/ext/ca_sweep_engine.c +90 -23
  41. data/ext/ca_sweep_engine.h +5 -6
  42. data/ext/caframe_csv_reader.c +1 -1
  43. data/ext/carray.h +3 -1
  44. data/ext/carray_access.c +98 -39
  45. data/ext/carray_address_basis.c +98 -24
  46. data/ext/carray_broadcast.c +2 -2
  47. data/ext/carray_call_cfunc.c +5 -8
  48. data/ext/carray_cast.c +311 -37
  49. data/ext/carray_cast_func.rb +37 -5
  50. data/ext/carray_conversion.c +56 -2
  51. data/ext/carray_copy.c +24 -0
  52. data/ext/carray_core.c +69 -6
  53. data/ext/carray_count.c +28 -9
  54. data/ext/carray_element.c +9 -3
  55. data/ext/carray_factorize.c +11 -11
  56. data/ext/carray_histogram.c +2 -2
  57. data/ext/carray_index_classifier.c +7 -5
  58. data/ext/carray_internal.h +94 -0
  59. data/ext/carray_kernels_binop.c +55 -55
  60. data/ext/carray_kernels_init.c +14 -14
  61. data/ext/carray_kernels_monop.c +90 -140
  62. data/ext/carray_lazy.c +16 -1
  63. data/ext/carray_loop.c +9 -2
  64. data/ext/carray_mask.c +118 -11
  65. data/ext/carray_median_percentile.c +13 -8
  66. data/ext/carray_memory_view.c +41 -22
  67. data/ext/carray_operator.c +72 -11
  68. data/ext/carray_order.c +52 -9
  69. data/ext/carray_partition.c +1 -1
  70. data/ext/carray_random.c +1 -1
  71. data/ext/carray_round.c +24 -1
  72. data/ext/carray_scatter.c +4 -0
  73. data/ext/carray_segment.c +1 -1
  74. data/ext/carray_slab.c +13 -4
  75. data/ext/carray_sort.c +6 -3
  76. data/ext/carray_sort_kernel.c +24 -0
  77. data/ext/carray_struct.c +21 -6
  78. data/ext/carray_test.c +85 -39
  79. data/ext/carray_time_text.c +6 -0
  80. data/ext/carray_utils.c +124 -54
  81. data/ext/mk_call_cfunc.rb +6 -11
  82. data/ext/mkkernel.rb +37 -27
  83. data/ext/version.h +4 -4
  84. data/lib/carray/axis_group.rb +9 -6
  85. data/lib/carray/basics.rb +26 -8
  86. data/lib/carray/bincount_nd.rb +8 -2
  87. data/lib/carray/block_iterator.rb +48 -21
  88. data/lib/carray/categorical.rb +21 -8
  89. data/lib/carray/categorical_iterator.rb +2 -1
  90. data/lib/carray/conditional.rb +52 -4
  91. data/lib/carray/const_string.rb +74 -0
  92. data/lib/carray/construct.rb +2 -2
  93. data/lib/carray/core_extensions.rb +19 -68
  94. data/lib/carray/data_type_extension.rb +1 -0
  95. data/lib/carray/fixlen_string.rb +83 -1
  96. data/lib/carray/frame/column_verbs.rb +61 -6
  97. data/lib/carray/frame/concat.rb +50 -6
  98. data/lib/carray/frame/csv_parser.rb +13 -4
  99. data/lib/carray/frame/frame.rb +38 -6
  100. data/lib/carray/frame/group.rb +67 -40
  101. data/lib/carray/frame/io.rb +10 -5
  102. data/lib/carray/frame/join.rb +20 -6
  103. data/lib/carray/frame/records.rb +6 -4
  104. data/lib/carray/frame/reshape.rb +25 -5
  105. data/lib/carray/frame/verbs.rb +78 -25
  106. data/lib/carray/frame/windows.rb +30 -6
  107. data/lib/carray/fuse_source.rb +4 -2
  108. data/lib/carray/iterator.rb +8 -0
  109. data/lib/carray/key_windows.rb +10 -4
  110. data/lib/carray/lazy.rb +12 -17
  111. data/lib/carray/methods/bin.rb +3 -0
  112. data/lib/carray/methods/composition.rb +1 -1
  113. data/lib/carray/methods/factorize.rb +2 -0
  114. data/lib/carray/methods/gather_nd.rb +4 -1
  115. data/lib/carray/methods/is_in.rb +23 -3
  116. data/lib/carray/methods/locate_addr.rb +63 -12
  117. data/lib/carray/methods/repeat.rb +11 -1
  118. data/lib/carray/record_order.rb +35 -6
  119. data/lib/carray/runtime.rb +10 -0
  120. data/lib/carray/segment_iterator.rb +19 -16
  121. data/lib/carray/serialize.rb +22 -0
  122. data/lib/carray/slab_iterator.rb +1 -1
  123. data/lib/carray/stack.rb +3 -3
  124. data/lib/carray/string.rb +6 -6
  125. data/lib/carray/string_operation_extension.rb +25 -4
  126. data/lib/carray/struct_builder.rb +4 -1
  127. data/lib/carray/time.rb +190 -46
  128. data/lib/carray/window_iterator.rb +48 -12
  129. data/yard-stubs/ca_obj_bitfield.rb +4 -1
  130. data/yard-stubs/ca_obj_refer.rb +3 -0
  131. data/yard-stubs/ca_obj_window.rb +2 -1
  132. data/yard-stubs/carray_access.rb +4 -2
  133. data/yard-stubs/carray_cast.rb +6 -0
  134. data/yard-stubs/carray_count.rb +6 -3
  135. data/yard-stubs/carray_math.rb +5 -0
  136. data/yard-stubs/carray_order.rb +6 -0
  137. data/yard-stubs/carray_scatter.rb +2 -1
  138. data/yard-stubs/carray_test.rb +18 -5
  139. metadata +2 -2
data/lib/carray/time.rb CHANGED
@@ -623,8 +623,57 @@ module CATimeOperandValues
623
623
  in_common_unit_with(lifted)
624
624
  end
625
625
 
626
+ # @overload pmax(other)
627
+ # The later time, or the longer duration, of each cell and `other`, cell
628
+ # by cell: `other` is an array of the same kind and shape, or one value
629
+ # (for a time a {CATime::Element}, a `Time`, a `Date`, `"2024-01-02"`; for
630
+ # a duration a {CATimedelta::Element}, `"3 days"`) for every cell. The
631
+ # two meet as a comparison does, so the result is in `self`'s unit when
632
+ # `other` sits on its grid and in the finer unit the two share otherwise.
633
+ # A masked cell on either side gives a masked cell.
634
+ # @param other [CATime, CATimedelta, CATime::Element, CATimedelta::Element, Time, Date, String]
635
+ # @return [CATime, CATimedelta]
636
+ # @raise [TypeError] when `other` is not of this kind.
637
+ def pmax (other)
638
+ pick_by_order(other) { |a, b| a < b }
639
+ end
640
+
641
+ # @overload pmin(other)
642
+ # The earlier time, or the shorter duration; see {#pmax}.
643
+ # @param other [CATime, CATimedelta, CATime::Element, CATimedelta::Element, Time, Date, String]
644
+ # @return [CATime, CATimedelta]
645
+ # @raise [TypeError] when `other` is not of this kind.
646
+ def pmin (other)
647
+ pick_by_order(other) { |a, b| a > b }
648
+ end
649
+
650
+ # A time or a duration has no NaN, so maximum / minimum (which let a NaN
651
+ # win) are pmax / pmin, and fmax / fmin are their other names.
652
+ alias_method :maximum, :pmax
653
+ alias_method :minimum, :pmin
654
+ alias_method :fmax, :pmax
655
+ alias_method :fmin, :pmin
656
+
626
657
  private
627
658
 
659
+ # `other` where the block says self's cell loses to it, else self's cell.
660
+ def pick_by_order (other)
661
+ return copy.tap { |out| out[] = UNDEF } if UNDEF.equal?(other)
662
+ a, b = comparable_pair(other)
663
+ b = b[0] unless other.is_a?(CArray) || other.is_a?(Array)
664
+ yield(a, b).then_else(b, a)
665
+ end
666
+
667
+ # Both branches of a selection in the unit a comparison meets them in, so
668
+ # the finer unit wins whichever branch it is.
669
+ def branches_in_common (x, y)
670
+ other, base = y.is_a?(CArray) && !y.scalar? ? [x, y] : [y, x]
671
+ return [x, y] if other.nil? || UNDEF.equal?(other)
672
+ a, b = base.comparable_pair(other)
673
+ b = b[0] unless other.is_a?(CArray) && !other.scalar?
674
+ base.equal?(y) ? [b, a] : [a, b]
675
+ end
676
+
628
677
  # The operand as an array of this kind in its own unit: one value read as
629
678
  # it is written, an Array or an array of another kind read value by value
630
679
  # in the unit they share, an UNDEF or a masked cell kept masked.
@@ -739,13 +788,19 @@ class CATime
739
788
  # @return [String]
740
789
  def to_s
741
790
  case unit.base
742
- when :Y then to_date.strftime("%Y")
743
- when :M then to_date.strftime("%Y-%m")
744
- when :W, :D then to_date.strftime("%Y-%m-%d")
745
- else to_time.iso8601(fractional_second_digits) # :h .. :as (time shown)
791
+ when :Y then signed_past_four_digits(to_date.strftime("%Y"))
792
+ when :M then signed_past_four_digits(to_date.strftime("%Y-%m"))
793
+ when :W, :D then signed_past_four_digits(to_date.strftime("%Y-%m-%d"))
794
+ else signed_past_four_digits(to_time.iso8601(fractional_second_digits)) # :h .. :as (time shown)
746
795
  end
747
796
  end
748
797
 
798
+ # A year past four digits carries its sign, which is what tells it from a
799
+ # date written without separators when the text is read back.
800
+ private def signed_past_four_digits (text)
801
+ text.start_with?("-") || to_date.year < 10000 ? text : "+#{text}"
802
+ end
803
+
749
804
  # @return [String] the instant plus its storage unit.
750
805
  def inspect
751
806
  tag = unit.count == 1 ? "#{value}#{unit.base}" : "#{value} @ #{unit}"
@@ -810,7 +865,7 @@ class CATime
810
865
  # @raise [TypeError, ArgumentError] on a non-timedelta operand, or a
811
866
  # pair that has no answer on self's grid.
812
867
  def +(td)
813
- shifted_by_duration(td, 1)
868
+ shifted_by_duration(one_step_of(td), 1)
814
869
  end
815
870
 
816
871
  # @overload -(other)
@@ -831,6 +886,10 @@ class CATime
831
886
  shifted_by_duration(other, -1)
832
887
  when CATime
833
888
  -(other - self)
889
+ when String, Time, Date
890
+ step = other.is_a?(String) ? one_step_of(other) : other
891
+ return shifted_by_duration(step, -1) unless step.equal?(other)
892
+ (CATime.wrap(CA_INT64([value]), unit: unit) - other)[0]
834
893
  else
835
894
  raise TypeError, "CATime::Element - #{other.class} is not allowed"
836
895
  end
@@ -851,6 +910,18 @@ class CATime
851
910
  # the duration is converted into it (truncated toward zero when finer; a
852
911
  # month or year duration on a fixed grid moves the date by months).
853
912
  # Mirrors the array CATime +/-.
913
+ # A String naming a duration ("3 days") as one step of it; anything else
914
+ # as it is.
915
+ def one_step_of(x)
916
+ return x unless x.is_a?(String)
917
+ res = begin
918
+ CATime::Resolution.parse(x)
919
+ rescue ArgumentError
920
+ return x
921
+ end
922
+ CATimedelta::Element.new(1, res)
923
+ end
924
+
854
925
  def shifted_by_duration(td, sign)
855
926
  # An array of durations gives an array of instants, anchored here.
856
927
  return sign > 0 ? td + self : (-td) + self if td.is_a?(CATimedelta)
@@ -1005,6 +1076,20 @@ class CATimedelta
1005
1076
  # @return [CATimedelta::Element]
1006
1077
  def -(other) = combined_with_duration(other, -1)
1007
1078
 
1079
+ # @return [CATimedelta::Element] this duration with its sign reversed.
1080
+ def -@ = Element.new(-value, unit)
1081
+
1082
+ # @return [CATimedelta::Element] the magnitude of this duration.
1083
+ def abs = Element.new(value.abs, unit)
1084
+
1085
+ # @overload coerce(number)
1086
+ # Lets a duration stand on the right of a number, so `2 * d` is `d * 2`.
1087
+ # @param number [Numeric]
1088
+ # @return [Array]
1089
+ def coerce (number)
1090
+ [LeftFactor.new(number), self]
1091
+ end
1092
+
1008
1093
  # @overload *(n)
1009
1094
  # Returns this duration scaled by an Integer.
1010
1095
  # @param n [Integer]
@@ -1203,6 +1288,13 @@ class CATime
1203
1288
  end
1204
1289
  end
1205
1290
 
1291
+ # The word form of {#+}.
1292
+ # @return [CATime]
1293
+ alias_method :add, :+
1294
+ # The word form of {#-}.
1295
+ # @return [CATime, CATimedelta]
1296
+ alias_method :sub, :-
1297
+
1206
1298
  # Whether a duration moves this time by calendar months: a month or year
1207
1299
  # duration on a time whose grid is fixed-length. A time on a month or year
1208
1300
  # grid adds it as months already, and a fixed duration on a calendar grid
@@ -1220,44 +1312,6 @@ class CATime
1220
1312
  CATimeUnitAlgebra.shift_months(face_storage, unit, months).time(unit: unit)
1221
1313
  end
1222
1314
 
1223
- # @overload pmax(other)
1224
- # The later of each cell and `other`, cell by cell: `other` is a time
1225
- # array of the same shape, or one time (a {CATime::Element}, a `Time`, a
1226
- # `Date`, `"2024-01-02"`) for every cell. The two meet as a comparison
1227
- # does, so the result is in `self`'s unit when `other` sits on its grid
1228
- # and in the finer unit the two share otherwise. A masked cell on either
1229
- # side gives a masked cell.
1230
- # @param other [CATime, CATime::Element, Time, Date, String]
1231
- # @return [CATime]
1232
- # @raise [TypeError] when `other` is not a time.
1233
- def pmax (other)
1234
- pick_by_order(other) { |a, b| a < b }
1235
- end
1236
-
1237
- # @overload pmin(other)
1238
- # The earlier of each cell and `other`; see {#pmax}.
1239
- # @param other [CATime, CATime::Element, Time, Date, String]
1240
- # @return [CATime]
1241
- # @raise [TypeError] when `other` is not a time.
1242
- def pmin (other)
1243
- pick_by_order(other) { |a, b| a > b }
1244
- end
1245
-
1246
- # A time has no NaN, so maximum / minimum (which let a NaN win) are pmax /
1247
- # pmin, and fmax / fmin are their other names.
1248
- alias_method :maximum, :pmax
1249
- alias_method :minimum, :pmin
1250
- alias_method :fmax, :pmax
1251
- alias_method :fmin, :pmin
1252
-
1253
- # `other` where the block says self's cell loses to it, else self's cell.
1254
- private def pick_by_order (other)
1255
- return copy.tap { |out| out[] = UNDEF } if UNDEF.equal?(other)
1256
- a, b = comparable_pair(other)
1257
- b = b[0] unless other.is_a?(CArray) || other.is_a?(Array)
1258
- yield(a, b).then_else(b, a)
1259
- end
1260
-
1261
1315
  # Element-wise comparison (< <= > >= <=>) rides the core comparison Face
1262
1316
  # gate (ca_face_reconcile_comparison in ext/ca_obj_face.c): the inherited
1263
1317
  # CArray operators descend to storage and reconcile a CATime RHS in a
@@ -1928,6 +1982,7 @@ class CATimedelta
1928
1982
  # @raise [TypeError] on incompatible operands.
1929
1983
  def /(other)
1930
1984
  given, other = other, CATimeUnitAlgebra.operand(other)
1985
+ other = CATimeUnitAlgebra.scalar_operand(other, unit) || other
1931
1986
  case other
1932
1987
  when Integer
1933
1988
  CATimeUnitAlgebra.trunc_divide(face_storage, other).timedelta(unit: unit)
@@ -1942,6 +1997,68 @@ class CATimedelta
1942
1997
  end
1943
1998
  end
1944
1999
 
2000
+ # A number on the left of a duration: only `n * duration` has an answer.
2001
+ class LeftFactor
2002
+ def initialize (factor)
2003
+ @factor = factor
2004
+ end
2005
+
2006
+ def * (duration) = duration * @factor
2007
+
2008
+ def method_missing (op, duration, *)
2009
+ raise TypeError, "#{@factor.class} #{op} #{duration.class} is not allowed"
2010
+ end
2011
+
2012
+ def respond_to_missing? (*) = true
2013
+ end
2014
+ private_constant :LeftFactor
2015
+
2016
+ # @overload coerce(number)
2017
+ # Lets a duration stand on the right of a number, so `2 * td` is `td * 2`.
2018
+ # @param number [Numeric]
2019
+ # @return [Array]
2020
+ def coerce (number)
2021
+ number.is_a?(Numeric) ? [LeftFactor.new(number), self] : super
2022
+ end
2023
+
2024
+ # @overload to_seconds
2025
+ # Each duration in seconds, exactly, as {Element#to_seconds} answers.
2026
+ # @return [CArray] an object array of Rational.
2027
+ # @raise [ArgumentError] for a :Y / :M unit, which has no exact seconds.
2028
+ def to_seconds
2029
+ unless CATimeUnitAlgebra::FIXED.key?(unit.base)
2030
+ raise ArgumentError,
2031
+ "CATimedelta #{unit} has no exact seconds (a :#{unit.base} is " \
2032
+ "calendar-variable); use to_seconds_approx for an estimate"
2033
+ end
2034
+ ratio = unit.tick_ratio
2035
+ face_storage.convert(:object) { |v| Rational(v) * ratio }
2036
+ end
2037
+
2038
+ # @overload to_seconds_approx
2039
+ # Each duration in seconds by nominal lengths, as
2040
+ # {Element#to_seconds_approx} answers.
2041
+ # @return [CArray] float64.
2042
+ def to_seconds_approx
2043
+ face_storage.to_type(:float64) * (unit.count * (Element::UNIT_TO_SECONDS[unit.base] || 1))
2044
+ end
2045
+
2046
+ # The word form of {#+}.
2047
+ # @return [CATimedelta, CATime]
2048
+ alias_method :add, :+
2049
+ # The word form of {#-}.
2050
+ # @return [CATimedelta]
2051
+ alias_method :sub, :-
2052
+ # The word form of {#*}.
2053
+ # @return [CATimedelta]
2054
+ alias_method :mul, :*
2055
+ # The word form of {#/}.
2056
+ # @return [CATimedelta, CArray]
2057
+ alias_method :div, :/
2058
+ # The word form of {#-@}.
2059
+ # @return [CATimedelta]
2060
+ alias_method :neg, :-@
2061
+
1945
2062
  # Element-wise comparison (< <= > >= <=>) rides the core comparison Face
1946
2063
  # gate (ca_face_reconcile_comparison): the inherited CArray operators
1947
2064
  # descend to storage and reconcile a CATimedelta RHS in a different unit
@@ -2138,8 +2255,12 @@ class CATimedelta
2138
2255
 
2139
2256
  # One duration as a length-1 duration array in its own unit, or nil.
2140
2257
  private def lift_value (operand)
2141
- if operand.is_a?(CATimedelta::Element)
2258
+ case operand
2259
+ when CATimedelta::Element
2142
2260
  CATimedelta.wrap(CA_INT64([operand.value]), unit: operand.unit)
2261
+ when String
2262
+ res = CATime::Resolution.parse(operand)
2263
+ CATimedelta.wrap(CA_INT64([res.count]), unit: res.base)
2143
2264
  end
2144
2265
  end
2145
2266
 
@@ -2152,6 +2273,8 @@ class CATimedelta
2152
2273
  when CATimedelta::Element
2153
2274
  lifted = CATimedelta.wrap(CA_INT64([operand.value]), unit: operand.unit)
2154
2275
  to_comparable(lifted)
2276
+ when String
2277
+ to_comparable(lift_value(operand))
2155
2278
  when Array, CArray
2156
2279
  # Durations written one by one, or an array that is not a duration
2157
2280
  # array (an object column of elements), read value by value.
@@ -2169,7 +2292,8 @@ class CATimedelta
2169
2292
  # scalar store round-trips with a fetch. A {Element} is reconciled to
2170
2293
  # self's unit via {#to_comparable} (lossless discipline; a `Time` /
2171
2294
  # `DateTime` is an absolute instant, not a duration, so it raises there).
2172
- # A bare Integer (the `.parent` raw-storage escape) and a String pass
2295
+ # A String that names a duration (`"3 days"`) is read as one. A bare
2296
+ # Integer (the `.parent` raw-storage escape) and any other String pass
2173
2297
  # through unchanged.
2174
2298
  # @param surface [Element, Integer, String]
2175
2299
  # @return [Integer, Object] the storage-domain value, or `surface`
@@ -2177,8 +2301,15 @@ class CATimedelta
2177
2301
  # @raise [TypeError, ArgumentError] on an unreconcilable surface / unit.
2178
2302
  def element_to_storage (surface)
2179
2303
  case surface
2180
- when Integer, String
2304
+ when Integer
2181
2305
  surface
2306
+ when String
2307
+ begin
2308
+ to_comparable(surface).parent[0]
2309
+ rescue ArgumentError => e
2310
+ raise unless e.message.start_with?("invalid unit spec")
2311
+ surface
2312
+ end
2182
2313
  else
2183
2314
  to_comparable(surface).parent[0]
2184
2315
  end
@@ -2266,11 +2397,24 @@ module CATimeLiteral
2266
2397
  sec: t.sec, sec_fraction: frac.zero? ? nil : Rational(frac), leftover: h[:leftover] }.compact
2267
2398
  end
2268
2399
 
2400
+ # Date._parse drops the sign of a year on its own or before a month, a day
2401
+ # of the year or a week, and reads a year below 100 as this century's, so
2402
+ # those are read here.
2403
+ def year_first_fields(spec)
2404
+ case spec
2405
+ when /\A([-+]\d{4,})\z/ then { year: $1.to_i }
2406
+ when /\A([-+]\d{4,})-(\d{1,2})\z/ then { year: $1.to_i, mon: $2.to_i }
2407
+ when /\A([-+]?\d{4,})-(\d{3})\z/ then { year: $1.to_i, yday: $2.to_i }
2408
+ when /\A([-+]?\d{4,})-W(\d{2})-(\d)\z/ then { cwyear: $1.to_i, cweek: $2.to_i, cwday: $3.to_i }
2409
+ end
2410
+ end
2411
+
2269
2412
  def parse_date_fields(spec, format)
2270
2413
  h =
2271
2414
  if format then epoch_fields(Date._strptime(spec, format))
2272
2415
  elsif spec =~ /\A(\d{4})-(\d{1,2})\z/ then { year: $1.to_i, mon: $2.to_i }
2273
2416
  elsif spec =~ /\A(\d{4})\z/ then { year: $1.to_i }
2417
+ elsif (year_first = year_first_fields(spec)) then year_first
2274
2418
  elsif (hour = hour_only_fields(spec)) then hour
2275
2419
  else Date._parse(spec)
2276
2420
  end
@@ -70,6 +70,8 @@ class CAWindowIterator < CAIterator
70
70
  # forward-looking). `bounds:` selects the margin policy (`:skip` /
71
71
  # `:nearest` / `:truncate`). `fill_value:` is a constant margin value
72
72
  # (an escape for `:constant` padding); when given it overrides `:skip`.
73
+ # Over a categorical or a const string, which hold no new value, it has
74
+ # to be a value some cell already holds.
73
75
  #
74
76
  # For backward compatibility `initialize(window_view)` with no ranges
75
77
  # accepts a CAWindow view (the old `CAWindowIterator.new(a.window(...))`
@@ -82,6 +84,8 @@ class CAWindowIterator < CAIterator
82
84
  # @param ranges [Array<Range>] per-axis offset ranges.
83
85
  # @param bounds [Symbol] `:skip` / `:nearest` / `:truncate`.
84
86
  # @param fill_value [Object, nil] constant margin value, overriding :skip.
87
+ # @raise [ArgumentError] when a categorical or const string source holds
88
+ # no cell with `fill_value` (raised when the windows are first read).
85
89
  def initialize (source, *ranges, bounds: :skip, fill_value: nil)
86
90
  if ranges.empty? && source.is_a?(CArray) && source.obj_type == CA_OBJ_WINDOW
87
91
  # Backward-compat: read geometry from a CAWindow view built by #window.
@@ -282,15 +286,32 @@ class CAWindowIterator < CAIterator
282
286
  def pad_face (src, lefts, rights, mode, value)
283
287
  storage = src.__send__(:face_storage)
284
288
  if mode == :constant && !value.nil?
285
- cell = src.__send__(:blank_like, [1])
286
- cell[0] = value
287
- padded = pad_source(storage, lefts, rights, :constant, cell.parent[0])
289
+ padded = pad_source(storage, lefts, rights, :constant, margin_storage(src, storage, value))
288
290
  else
289
291
  padded = pad_source(storage, lefts, rights, mode, value)
290
292
  end
291
293
  padded.face_lift(src)
292
294
  end
293
295
 
296
+ # The storage of a `:constant` margin value. A Face that holds new values
297
+ # turns the value into storage; one that holds none (a const string's
298
+ # records index a shared pool, a categorical's codes index its labels) can
299
+ # only repeat the storage of a cell that already holds the value.
300
+ def margin_storage (src, storage, value)
301
+ if src.__send__(:holds_new_values?)
302
+ cell = src.__send__(:blank_like, [1])
303
+ cell[0] = value
304
+ return cell.parent[0]
305
+ end
306
+ hit = src.eq(value).where
307
+ if hit.elements == 0
308
+ raise ArgumentError,
309
+ "windows: fill_value #{value.inspect} is not a value of this " \
310
+ "#{src.class}, which can only repeat a value it already holds"
311
+ end
312
+ storage.flatten[hit[0]]
313
+ end
314
+
294
315
  # Edge-replicate the margins of a padded buffer whose interior already holds
295
316
  # the source. Per axis, the left margin rows copy the first interior row and
296
317
  # the right margin rows copy the last; done axis by axis over the whole
@@ -567,11 +588,15 @@ class CAWindowIterator < CAIterator
567
588
  return nil unless op == :mean || OFFSET_FOLD.key?(op)
568
589
  # The elementwise kernels act on a Face's storage; its reductions are its own.
569
590
  return nil if @source.face?
591
+ # Fixed-length cells have no elementwise kernels and no neutral value.
592
+ return nil if @source.data_type == CA_FIXLEN
593
+ # An undefined constant margin is a masked one, which a fold must skip.
594
+ return nil if @fill_value.equal?(UNDEF)
570
595
  return nil if @widths.any? { |width| width > OFFSET_FOLD_MAX_WIDTH }
571
- return nil if neutral_value(op).nil?
572
596
  # pmin / pmax let NaN lose, so a window holding only NaN would fold to the
573
597
  # neutral value; the core answers NaN there.
574
598
  return nil if (op == :min || op == :max) && source_has_nan?
599
+ return nil if neutral_value(op).nil?
575
600
 
576
601
  folded = op == :mean ? fold_mean : accumulate_offsets(op)
577
602
  folded = mask_empty_windows(folded) if EMPTY_WINDOW_IS_UNDEFINED.include?(op)
@@ -640,6 +665,7 @@ class CAWindowIterator < CAIterator
640
665
  end
641
666
 
642
667
  def source_has_nan?
668
+ return true if @bounds == :constant && @fill_value.is_a?(Float) && @fill_value.nan?
643
669
  return false unless @source.float? || @source.data_type == CA_OBJECT
644
670
  @source.is_nan.any
645
671
  end
@@ -659,9 +685,9 @@ class CAWindowIterator < CAIterator
659
685
  # What stands in for a cell the fold must not see: a margin the boundary
660
686
  # policy does not fill, or a masked cell of the source. For sum, prod, all
661
687
  # and any that is the operation's identity. For min and max it is any value
662
- # that cannot win, and the array's own extreme is one -- so no table of
663
- # per-type limits is needed. Nil means there is none to be had (a source
664
- # masked everywhere), and the caller delegates instead.
688
+ # that cannot win, and the extreme of the array and its constant margin is
689
+ # one -- so no table of per-type limits is needed. Nil means there is none
690
+ # to be had (a source masked everywhere), and the caller delegates instead.
665
691
  def neutral_value (op)
666
692
  @neutral_value ||= {}
667
693
  return @neutral_value[op] if @neutral_value.key?(op)
@@ -678,7 +704,11 @@ class CAWindowIterator < CAIterator
678
704
 
679
705
  def source_extreme (op)
680
706
  value = @source.send(op)
681
- value.equal?(UNDEF) ? nil : value
707
+ return nil if value.equal?(UNDEF)
708
+ return value unless @bounds == :constant
709
+ margin = @fill_value || 0
710
+ margin = margin ? 1 : 0 if @source.boolean? # the core answers 0 or 1
711
+ [value, margin].public_send(op)
682
712
  end
683
713
 
684
714
  # The buffer the offsets are read from: the source with its margins filled
@@ -722,13 +752,19 @@ class CAWindowIterator < CAIterator
722
752
  # real values, so the count is the whole window and one Integer says it;
723
753
  # `:skip` counts the in-bounds offsets, which is separable -- the count on
724
754
  # one axis depends on that axis alone. A masked source has to be counted for
725
- # real, by accumulating over the same offsets.
755
+ # real, by accumulating over the same offsets; a cell of a constant margin
756
+ # counts once, whatever its value.
726
757
  def window_cell_counts
727
758
  return @window_cell_counts unless @window_cell_counts.nil?
728
759
  @window_cell_counts =
729
760
  if @source.has_mask?
730
- accumulate_offsets(:sum, pad_for(@source.is_not_masked.int64, 0),
731
- :add!, CA_INT64)
761
+ present = @source.is_not_masked.int64
762
+ present = if @bounds == :constant
763
+ pad_source(present, @lefts, @rights, :constant, 1)
764
+ else
765
+ pad_for(present, 0)
766
+ end
767
+ accumulate_offsets(:sum, present, :add!, CA_INT64)
732
768
  elsif @bounds != :skip
733
769
  @widths.inject(:*)
734
770
  else
@@ -806,7 +842,7 @@ class CAWindowIterator < CAIterator
806
842
  return count_not_masked(**kw) if args.empty?
807
843
  # The sliding_windows view is a CAStride, so its #count is not shadowed;
808
844
  # dispatch CArray#count explicitly anyway, matching the family regularity.
809
- CArray.instance_method(:count).bind_call(sliding_view, *args, axis: @window_axes,
845
+ count_cells(sliding_view, *args, axis: @window_axes,
810
846
  **CAIterator.reduce_keywords(:count, kw))
811
847
  end
812
848
 
@@ -30,7 +30,10 @@ class CArray
30
30
  # narrowest unsigned type that holds the field is used: 1 bit →
31
31
  # `:boolean`, 2..8 → `:uint8`, 9..16 → `:uint16`, 17..32 → `:uint32`,
32
32
  # 33..64 → `:uint64`. A write keeps the low bits of the value that
33
- # fit the field, signed or not (-3 into a 3-bit field is `0b101`).
33
+ # fit the field, signed or not, for an Integer of any size (-3 into a
34
+ # 3-bit field is `0b101`, 300 is `0b100`). The same holds for a view
35
+ # taken of the field (a slice, an index, a mask selection). A String
36
+ # is read as a decimal integer, a Float through `Integer()`.
34
37
  #
35
38
  # @overload bitfield(range, type = nil)
36
39
  # @param range [Integer, Range] bit position (single bit) or bit
@@ -35,6 +35,9 @@ class CArray
35
35
  # @raise [ArgumentError] when the byte widths do not divide
36
36
  # evenly, when `offset` is negative, or when the requested
37
37
  # view extends past the parent's data.
38
+ # @raise [TypeError] on a Face, for a data type that is neither its
39
+ # surface type nor its storage type: `to_type` gives the values,
40
+ # `parent.refer` the raw storage.
38
41
  # @raise [CArray::DataTypeError] when reinterpreting a CA_OBJECT parent
39
42
  # with a non-object `data_type`, or a parent of another data type as
40
43
  # `:boolean` (a boolean cell holds 0 or 1; compare instead, `ne(0)`).
@@ -54,7 +54,8 @@ class CArray
54
54
  # or when `bounds` is not a recognised value.
55
55
  # @raise [IndexError] when `bounds` is `"strict"` and a range reaches
56
56
  # outside `self`, or `"ruby"` and a range reaches past the end or
57
- # before the first cell counted from the end.
57
+ # before the first cell counted from the end, or `"nearest"` and a
58
+ # range takes cells of an axis of length 0.
58
59
  def window(*ranges, fill_value: 0, bounds: "fill"); end
59
60
 
60
61
  # @!endgroup
@@ -90,7 +90,8 @@ class CArray
90
90
  # or a CArray of such addresses (arbitrary shape). A negative
91
91
  # address counts from the end.
92
92
  # @return [Array<Integer>, Array<CArray>] one entry per axis.
93
- # @raise [IndexError] when any `addr` is out of range.
93
+ # @raise [IndexError] when any `addr` is out of range, or when `addr` is
94
+ # float, complex, object or boolean.
94
95
  def addr2index(addr); end
95
96
 
96
97
  # @overload index2addr(*index)
@@ -101,7 +102,8 @@ class CArray
101
102
  # must match that shape). Mask propagates from the inputs.
102
103
  # @param index [Array<Integer, CArray>] one entry per axis.
103
104
  # @return [Integer, CArray] the flat address(es).
104
- # @raise [IndexError] on an out-of-range index.
105
+ # @raise [IndexError] on an out-of-range index, or an index array that
106
+ # is float, complex, object or boolean.
105
107
  # @raise [ArgumentError] on shape mismatch between non-scalar inputs.
106
108
  def index2addr(*index); end
107
109
 
@@ -490,6 +490,12 @@ class CArray
490
490
  # that does not fit an integer type takes `:int64`. With no array or
491
491
  # data_type representation, every argument promotes by its own type.
492
492
  #
493
+ # This is the type an operator computes in: `int32_array + float32_array`
494
+ # is `:float32`. Arrays joined value for value ({CArray.stack},
495
+ # {CArray.meld}, {CArray.concatenate}) take the type {.promote_list}
496
+ # gives, which is `:float64` for that pair, since `float32` does not
497
+ # hold every `int32` exactly.
498
+ #
493
499
  # Integer args are interpreted as *values*, not as data_type codes:
494
500
  # `result_type(8)` returns `:int64` because the value 8 is an Integer,
495
501
  # not because a data_type code equals 8. Use `result_type(:int64)` or
@@ -36,6 +36,9 @@ class CArray
36
36
  # count).
37
37
  # - `v == UNDEF`: forwards to {#count_masked} (UNDEF is mask-state
38
38
  # vocabulary, not a value).
39
+ # - `v` is `nil`, a String, a Symbol or another value that is not a
40
+ # number, on an array that is not `:object` or `:fixlen`: `0`. It
41
+ # is no value of such an array, so no cell equals it.
39
42
  # - `v` is a CArray: broadcasts; each `v[k]` is counted
40
43
  # independently and stacked into a result whose trailing axes
41
44
  # have shape `v.shape`. Not supported when `self.data_type` is
@@ -43,9 +46,9 @@ class CArray
43
46
  # - `self.data_type == :boolean`: `v` must be `true` / `false`, or
44
47
  # the integer literal `1` (= true) / `0` (= false) — boolean
45
48
  # stores 0/1, so `count(1)` == `count(true)`. Any other value
46
- # (`2`, `1.0`, `nil`, …) raises `TypeError`.
47
- # - `self` is numeric, `v` is scalar: `v` must be numeric (true /
48
- # false are rejected -- they are the boolean array's domain).
49
+ # (`2`, `1.0`, …) raises `TypeError`.
50
+ # - `self` is numeric, `v` is scalar: `true` and `false` are the
51
+ # numbers `1` and `0`, as they are to {#eq}.
49
52
  # `v` (a scalar or an array) is compared in the type it and the
50
53
  # array match in, as {#eq} compares: `CA_INT32([1, 2]).count(1.5)`
51
54
  # is `0`, `CA_UINT8([255]).count(-1)` is `0` (-1 is not 255), and an
@@ -784,6 +784,11 @@ class CArray
784
784
  # point equality is exact here; for a tolerant comparison see
785
785
  # {#is_close} (absolute) or {#is_equiv} (relative). Integers compare
786
786
  # by value, as for {#<}.
787
+ #
788
+ # `eq(UNDEF)` asks the mask, as {#is_masked} does. `nil`, a String, a
789
+ # Symbol or another value that is not a number is no value of a numeric
790
+ # or boolean array, so `eq` is false in every cell there; an `:object`
791
+ # or `:fixlen` array compares it like any other value.
787
792
  # @param other [CArray, Numeric, Object] comparand, broadcast to
788
793
  # `self.shape`.
789
794
  # @return [CArray] `:boolean`, same shape as `self`.
@@ -24,6 +24,7 @@ class CArray
24
24
  # @return [CArray] a fresh array shaped like `idx`; a Face of the same
25
25
  # type when `self` is a Face.
26
26
  # @raise [ArgumentError] if `lval` / `uval` is given and `self` is a Face.
27
+ # @raise [IndexError] when `idx` is float, complex, object or boolean.
27
28
  def project(idx, lval = nil, uval = nil); end
28
29
 
29
30
  # sort / sort_copy stubs live in yard-stubs/carray_sort.rb (same
@@ -176,6 +177,11 @@ class CArray
176
177
  # nothing: `nil`, or `UNDEF` in that cell of the result. The same
177
178
  # holds for {#bsearch} and {#search_nearest}.
178
179
  #
180
+ # `nil` is no value of an array that is not `:object`, so it finds
181
+ # nothing there; an `:object` array looks for it like any other value.
182
+ # {#bsearch} and {#search_nearest} need an order or a distance, which
183
+ # `nil` does not have, and raise.
184
+ #
179
185
  # A time array (`CATime`, `CATimedelta`) takes a value of another
180
186
  # unit, a `Time` or a String: when it is off the array's grid the
181
187
  # two are compared in the finer unit, so it matches nothing (and
@@ -45,7 +45,8 @@ class CArray
45
45
  # @param addrs [CArray, Array<Integer>]
46
46
  # @param vals [CArray, Numeric]
47
47
  # @return [self]
48
- # @raise [IndexError] for out-of-range addresses.
48
+ # @raise [IndexError] for out-of-range addresses, or when `addrs` is
49
+ # float, complex, object or boolean.
49
50
  # @raise [CArray::DataTypeError] for non-numeric `data_type`.
50
51
  def scatter_add!(addrs, vals); end
51
52
 
@@ -13,6 +13,7 @@ class CArray
13
13
  # @return [Boolean]
14
14
  # @raise [ArgumentError] if the number of indices does not match
15
15
  # `ndim`.
16
+ # @raise [TypeError] if an index is not an Integer.
16
17
  def valid_index?(*idx); end
17
18
 
18
19
  # @overload valid_addr?(addr)
@@ -21,11 +22,16 @@ class CArray
21
22
  # @param addr [Integer] flat address into the contiguous element
22
23
  # sequence.
23
24
  # @return [Boolean]
25
+ # @raise [TypeError] if `addr` is not an Integer.
24
26
  def valid_addr?(addr); end
25
27
 
26
28
  # @overload same_shape?(other)
27
- # Returns `true` if `other` has the same shape as `self`.
28
- # @param other [CArray]
29
+ # Returns `true` if `other` has the same shape as `self`. Only the
30
+ # shape is compared, whatever the data types of the two. A scalar has
31
+ # the same shape as any array. An `other` that is not a CArray is read
32
+ # as an object array: a nested Array by its nesting, anything else as
33
+ # a scalar.
34
+ # @param other [CArray, Object]
29
35
  # @return [Boolean]
30
36
  def same_shape?(other); end
31
37
 
@@ -55,9 +61,10 @@ class CArray
55
61
  # @overload eql?(other)
56
62
  # Returns `true` under Hash-invariant semantics: `data_class`,
57
63
  # `data_type`, shape, and mask state must all match, and elements
58
- # are compared bitwise (for numeric types) or via `Object#eql?`
59
- # (for `:object` arrays), so -0.0 and 0.0 differ and two NaNs are
60
- # `eql?` only with the same bits.
64
+ # are compared bitwise (for numeric types) or, for `:object` arrays, via
65
+ # `Object#eql?` with an object equal to itself first, as in
66
+ # `Array#eql?`. In a numeric array -0.0 and 0.0 differ and two NaNs
67
+ # are `eql?` only with the same bits.
61
68
  # Two Faces are `eql?` only when they are of the same class, share
62
69
  # their state (a time array's unit, a categorical's labels) and hold
63
70
  # the same storage.
@@ -72,7 +79,13 @@ class CArray
72
79
  # `ndim`, `bytes`, `elements`, shape, scalar-ness, and mask
73
80
  # presence; for unmasked arrays, samples the leading 64 bytes of
74
81
  # data. Masked arrays skip the data sample.
82
+ #
83
+ # An `:object` array samples the `#hash` of its cells, so an array in
84
+ # a cell is hashed in turn. {#==}, {#eql?} and `Marshal.dump` walk into
85
+ # cells the same way, and all of them refuse arrays nested more than
86
+ # 256 levels deep.
75
87
  # @return [Integer]
88
+ # @raise [ArgumentError] when object cells nest more than 256 levels.
76
89
  def hash; end
77
90
 
78
91
  # @!endgroup