carray-jit 0.1.2 → 0.1.4

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +917 -3
  3. data/README.md +14 -12
  4. data/carray-jit.gemspec +1 -3
  5. data/docs/00_Introduction.md +5 -3
  6. data/docs/01_GettingStarted.md +1 -1
  7. data/docs/02_KernelShapes.md +94 -15
  8. data/docs/03_SupportedFeatures.md +665 -32
  9. data/docs/04_Compiling.md +34 -7
  10. data/docs/05_DesignNotes.md +3 -3
  11. data/docs/06_Cheatsheet.md +200 -7
  12. data/docs/07_StepByStep.ja.md +533 -0
  13. data/docs/07_StepByStep.md +534 -0
  14. data/docs/08_LanguageReference.md +427 -0
  15. data/examples/README.md +12 -0
  16. data/examples/applications/alarm.rb +121 -0
  17. data/examples/applications/collatz.rb +105 -0
  18. data/examples/applications/cubic_spline.rb +331 -0
  19. data/examples/applications/dithering.rb +144 -0
  20. data/examples/applications/group_stats.rb +115 -0
  21. data/examples/applications/lookup.rb +126 -0
  22. data/examples/applications/median_filter.rb +153 -0
  23. data/examples/applications/parcel_ascent.rb +220 -0
  24. data/examples/applications/point_in_polygon.rb +111 -0
  25. data/examples/applications/random_walk.rb +98 -0
  26. data/examples/applications/van_der_pol.rb +186 -0
  27. data/examples/applications/wet_bulb.rb +140 -0
  28. data/examples/features/10_complex.rb +14 -4
  29. data/examples/features/15_loops.rb +7 -1
  30. data/lib/carray/jit/access.rb +14 -0
  31. data/lib/carray/jit/analyzer.rb +2218 -140
  32. data/lib/carray/jit/block_reader.rb +37 -6
  33. data/lib/carray/jit/c_function.rb +961 -86
  34. data/lib/carray/jit/c_generator.rb +1970 -191
  35. data/lib/carray/jit/call.rb +68 -0
  36. data/lib/carray/jit/compiler.rb +102 -14
  37. data/lib/carray/jit/expression.rb +271 -33
  38. data/lib/carray/jit/kernel.rb +382 -32
  39. data/lib/carray/jit/node.rb +374 -9
  40. data/lib/carray/jit/sorting_networks.rb +182 -0
  41. data/lib/carray/jit/type_assignment.rb +424 -35
  42. data/lib/carray/jit/version.rb +1 -1
  43. data/lib/carray/jit.rb +547 -77
  44. metadata +23 -8
  45. data/ext/carray_jit_access/carray_jit_access.c +0 -460
  46. data/ext/carray_jit_access/extconf.rb +0 -8
@@ -61,15 +61,24 @@ class CArray
61
61
  end
62
62
 
63
63
  class LocalRead < Node
64
- # The C variable this read resolves to. A Ruby local may hold an
65
- # Integer at one point in the body and a Float at another; a C variable
66
- # cannot, so each type gets its own.
67
- attr_accessor :binding_name
64
+ # Which of the local's variables this read resolves to, counted from 1.
65
+ # A Ruby local may hold an Integer at one point in the body and a Float
66
+ # at another; a C variable cannot, so each type gets its own.
67
+ attr_accessor :binding
68
+ # Set only on a read the generator makes of one partial accumulator of
69
+ # a split fold: which one.
70
+ attr_accessor :lane
68
71
  attr_reader :name
69
72
  def initialize (name, location = nil)
70
73
  super(location)
71
74
  @name = name
72
75
  end
76
+ # What says two reads are of one variable: the name the block wrote and
77
+ # which of its bindings. Not a spelling -- the block may itself write a
78
+ # local spelled like another one's second binding.
79
+ def local
80
+ [@name, @binding]
81
+ end
73
82
  end
74
83
 
75
84
  # A scalar the block closed over; passed in as a kernel argument.
@@ -110,6 +119,55 @@ class CArray
110
119
  end
111
120
  end
112
121
 
122
+ # `x.clamp(low, high)`: the value, or whichever bound it ran past.
123
+ #
124
+ # Ruby hands back the receiver or one of the bounds, so which type the
125
+ # answer has is decided by the value -- which is why all three have to
126
+ # be the same type here, and why the answer is then the receiver's.
127
+ class Clamp < Node
128
+ attr_reader :value, :low, :high
129
+ def initialize (value, low, high, location = nil)
130
+ super(location)
131
+ @value = value
132
+ @low = low
133
+ @high = high
134
+ end
135
+ def children
136
+ [@value, @low, @high]
137
+ end
138
+ end
139
+
140
+ # `x.nan?` and `x.finite?`: a question about a number, answered true or
141
+ # false whatever the number's width. `infinite?` is not one of these --
142
+ # Ruby answers it with nil or 1 or -1, which is not a boolean and has no
143
+ # nil to be.
144
+ class NumericPredicate < Node
145
+ attr_reader :name, :operand
146
+ def initialize (name, operand, location = nil)
147
+ super(location)
148
+ @name = name
149
+ @operand = operand
150
+ end
151
+ def children
152
+ [@operand]
153
+ end
154
+ end
155
+
156
+ # Whether a pointer parameter was handed an address at all: `grad.nil?`,
157
+ # `grad == nil`, and `if grad` where a condition is read. A callback's
158
+ # caller decides that per call -- NLopt hands a derivative-free method's
159
+ # objective no gradient to fill -- so it is a question the body asks,
160
+ # not one the declaration can settle. `null` is true for the spelling
161
+ # that asks whether it is missing.
162
+ class PointerTest < Node
163
+ attr_reader :name, :null
164
+ def initialize (name, null, location = nil)
165
+ super(location)
166
+ @name = name
167
+ @null = null
168
+ end
169
+ end
170
+
113
171
  # Zero of whatever type another expression has.
114
172
  #
115
173
  # A sum has to start from a zero of the summand's type: start it from an
@@ -145,12 +203,21 @@ class CArray
145
203
  # Nothing is written through it: a kernel writes the cell its outer
146
204
  # indices are on, and the inner loop is what runs within that cell.
147
205
  class InnerLoop < Node
148
- attr_reader :index, :from, :to, :statements
149
- def initialize (index, from, to, statements, location = nil)
206
+ # `step` is the stride the loop counts by, and `to` is exclusive of it:
207
+ # a descending loop ends one below the last index it visits, as an
208
+ # extent written with `step` does.
209
+ attr_reader :index, :from, :to, :step, :statements
210
+ # The locals its block declares, as [name, binding, type], the arrays it
211
+ # declares, as [name, binding, storage, shape], and the ones that were
212
+ # live when the loop was entered, by name as [type, binding] -- all
213
+ # settled by the typing.
214
+ attr_accessor :declarations, :array_declarations, :entering
215
+ def initialize (index, from, to, statements, location = nil, step = 1)
150
216
  super(location)
151
217
  @index = index
152
218
  @from = from
153
219
  @to = to
220
+ @step = step
154
221
  @statements = statements
155
222
  end
156
223
  def children
@@ -333,6 +400,41 @@ class CArray
333
400
  end
334
401
  end
335
402
 
403
+ # A draw from a captured CArray::Rng.
404
+ #
405
+ # The generator is not a function this kernel closed over -- nothing of
406
+ # it is compiled here. Its C arrives as text from CArray, is pasted into
407
+ # the preamble beside the helpers this compiler writes itself, and the
408
+ # draw is a call to it. So the sequence a kernel gets is the sequence
409
+ # `CArray#random!` gets, because it is the same code advancing the same
410
+ # cells and not a second implementation agreeing with the first.
411
+ #
412
+ # The state is an ordinary captured array under a name this compiler made
413
+ # up, handed over as an address the way any array a C function writes
414
+ # through is. That is what carries the sequence across the call: the
415
+ # cells the kernel advanced are the cells the generator still holds when
416
+ # it comes back.
417
+ class RandomDraw < Node
418
+ # The name the block reached the generator by, for a message to say.
419
+ attr_reader :generator
420
+ # The made-up name of its state array.
421
+ attr_reader :state
422
+ # What this draw answers: `:rand` a double in [0.0, 1.0), `:bits` the
423
+ # raw word the generator produced. Both advance the same state by one
424
+ # draw -- one step of the generator read two ways, not two sequences --
425
+ # so a kernel mixing them is still walking the one sequence.
426
+ attr_reader :kind
427
+ def initialize (generator, state, kind, location = nil)
428
+ super(location)
429
+ @generator = generator
430
+ @state = state
431
+ @kind = kind
432
+ end
433
+ def children
434
+ []
435
+ end
436
+ end
437
+
336
438
  # `p[i]` where `p` is a pointer parameter of a compiled function.
337
439
  #
338
440
  # Not an ElementRead: an array's cell is reached through a base and a
@@ -524,18 +626,49 @@ class CArray
524
626
  end
525
627
 
526
628
  class Assignment < Node
527
- attr_accessor :binding_name
528
- attr_reader :name, :expression
529
- def initialize (name, expression, location = nil)
629
+ # Which of the local's variables this writes, as LocalRead#binding.
630
+ attr_accessor :binding
631
+ # Which scope the local belongs to, counted out from the kernel's block
632
+ # -- or a function's -- at 0, one more for each inner loop's block.
633
+ attr_reader :name, :expression, :scope
634
+ def initialize (name, expression, location = nil, scope = 0)
530
635
  super(location)
531
636
  @name = name
532
637
  @expression = expression
638
+ @scope = scope
639
+ end
640
+ def local
641
+ [@name, @binding]
533
642
  end
534
643
  def children
535
644
  [@expression]
536
645
  end
537
646
  end
538
647
 
648
+ # `prev, cur = cur, prev + cur`
649
+ #
650
+ # Ruby settles every value on the right before it writes any of them, so
651
+ # the temporaries below are the statement rather than a way of writing
652
+ # it: without them the line above would write `prev` and then read the
653
+ # new one back while working out `cur`, which is the one thing the
654
+ # spelling exists to prevent.
655
+ #
656
+ # Held as the statements it became -- one assignment to a name of its own
657
+ # per value, in the order they were written, and then the writes. So
658
+ # everything downstream reads ordinary assignments and writes: the types,
659
+ # the masks a local carries, the C names, the order the body is swept in.
660
+ # Nothing here has to know that a parallel assignment is what they were.
661
+ class ParallelAssignment < Node
662
+ attr_reader :statements
663
+ def initialize (statements, location = nil)
664
+ super(location)
665
+ @statements = statements
666
+ end
667
+ def children
668
+ @statements
669
+ end
670
+ end
671
+
539
672
  # A cell of an array: out[i, j] = ...
540
673
  #
541
674
  # Writes are always at the cell the loop is on. Writing elsewhere would
@@ -558,8 +691,240 @@ class CArray
558
691
  end
559
692
  end
560
693
 
694
+ # `w = CArray.double(9)`, `CArray.new(:int64, [256])`, or
695
+ # `CArray.empty(:float64, [9])`: an array the body owns.
696
+ #
697
+ # It is declared at the head of the C block its Ruby scope stands for, as
698
+ # a local is, and lives on that block's stack. The zeroed spellings are
699
+ # cleared where the statement stands -- in Ruby a fresh array is made
700
+ # there each time the line runs, and the clearing is what says the same
701
+ # thing in C. `empty` says nothing there, and its cells are whatever the
702
+ # stack held.
703
+ #
704
+ # Not an ElementWrite or an Assignment. Those are tied to the operand
705
+ # tables -- the line-up, the dispatcher, the extents, the masks, the box a
706
+ # view is transferred in -- and this array is in none of them: the block
707
+ # wrote its shape, so its strides are constants and its bounds are settled
708
+ # either where the block is read or at the access. `PointerRead` stands
709
+ # apart for the same reason.
710
+ class LocalArrayDeclaration < Node
711
+ # Which of the name's variables this declares, as LocalRead#binding.
712
+ attr_accessor :binding
713
+ # `zeroed` is false for the `empty` spelling.
714
+ # `scope` is counted the way Assignment#scope is.
715
+ # `heap` is true where the array does not go on the stack: its shape is
716
+ # larger than a frame should hold, or is not known until the kernel
717
+ # runs. Then it is one allocation at the kernel's entry and a free at
718
+ # its exit, and the name is a pointer rather than an automatic array --
719
+ # which changes the declaration and nothing about how a cell is
720
+ # reached.
721
+ attr_reader :name, :storage, :shape, :zeroed, :scope, :heap
722
+ def initialize (name, storage, shape, zeroed, location = nil, scope = 0,
723
+ heap = false)
724
+ super(location)
725
+ @name = name
726
+ @storage = storage
727
+ @shape = shape
728
+ @zeroed = zeroed
729
+ @scope = scope
730
+ @heap = heap
731
+ end
732
+ def local
733
+ [@name, @binding]
734
+ end
735
+ # The shape's axes, as Nodes, for whatever has to walk them: an extent
736
+ # the block wrote as an expression is an expression like any other, and
737
+ # is typed and checked as one.
738
+ def children
739
+ @shape.filter_map { |extent|
740
+ extent.node if extent.is_a?(LocalArrayExtent)
741
+ }
742
+ end
743
+ end
744
+
745
+ # `w[k]`, `w[j + 1]`, `w[counts[i]]`.
746
+ #
747
+ # `subscripts` holds one [index, offset] pair per axis, the shape
748
+ # ElementRead's take: an index with a literal offset where the reach was
749
+ # settled as the block was read, or [nil, expression] where the position
750
+ # is one only the running kernel knows and is checked there.
751
+ class LocalArrayRead < Node
752
+ attr_accessor :binding
753
+ attr_reader :name, :storage, :shape, :subscripts
754
+ def initialize (name, storage, shape, subscripts, location = nil)
755
+ super(location)
756
+ @name = name
757
+ @storage = storage
758
+ @shape = shape
759
+ @subscripts = subscripts
760
+ end
761
+ def local
762
+ [@name, @binding]
763
+ end
764
+ def children
765
+ @subscripts.filter_map { |_index, offset| offset if offset.is_a?(Node) }
766
+ end
767
+ end
768
+
769
+ class LocalArrayWrite < Node
770
+ attr_accessor :binding
771
+ attr_reader :name, :storage, :shape, :subscripts, :expression
772
+ def initialize (name, storage, shape, subscripts, expression,
773
+ location = nil)
774
+ super(location)
775
+ @name = name
776
+ @storage = storage
777
+ @shape = shape
778
+ @subscripts = subscripts
779
+ @expression = expression
780
+ end
781
+ def local
782
+ [@name, @binding]
783
+ end
784
+ def children
785
+ [@expression] +
786
+ @subscripts.filter_map { |_index, offset| offset if offset.is_a?(Node) }
787
+ end
788
+ end
789
+
790
+ # One axis of a local array whose length the block wrote as an
791
+ # expression over captured integers rather than as a number.
792
+ #
793
+ # Not a Node: it stands in a shape, where every other entry is an
794
+ # Integer, and what a shape is asked is how long an axis is -- not what
795
+ # type it computes in. `text` is what the block wrote, which is what
796
+ # makes two of these the same extent: the expression is worked out at the
797
+ # kernel's entry, so two declarations that wrote the same thing are one C
798
+ # array, exactly as two that wrote the same number are.
799
+ class LocalArrayExtent
800
+ attr_reader :text, :node
801
+ def initialize (text, node)
802
+ @text = text
803
+ @node = node
804
+ end
805
+ def == (other)
806
+ other.is_a?(LocalArrayExtent) && other.text == @text
807
+ end
808
+ alias eql? ==
809
+ def hash
810
+ @text.hash
811
+ end
812
+ def to_s
813
+ @text
814
+ end
815
+ end
816
+
817
+ # `w[k] = UNDEF`: marks the cell missing and leaves its bytes alone.
818
+ #
819
+ # Beside LocalArrayWrite for the reason MaskWrite stands beside
820
+ # ElementWrite: UNDEF is a mark rather than a value, so there is no
821
+ # expression here to compute or to store.
822
+ class LocalArrayMaskWrite < Node
823
+ attr_accessor :binding
824
+ attr_reader :name, :storage, :shape, :subscripts
825
+ def initialize (name, storage, shape, subscripts, location = nil)
826
+ super(location)
827
+ @name = name
828
+ @storage = storage
829
+ @shape = shape
830
+ @subscripts = subscripts
831
+ end
832
+ def local
833
+ [@name, @binding]
834
+ end
835
+ def children
836
+ @subscripts.filter_map { |_index, offset| offset if offset.is_a?(Node) }
837
+ end
838
+ end
839
+
840
+ # `w[k] == UNDEF`, and `!=` with `negated`: reads the shadow byte beside
841
+ # the cell rather than the cell.
842
+ class LocalArrayMaskTest < Node
843
+ attr_accessor :binding
844
+ attr_reader :name, :storage, :shape, :subscripts, :negated
845
+ def initialize (name, storage, shape, subscripts, negated, location = nil)
846
+ super(location)
847
+ @name = name
848
+ @storage = storage
849
+ @shape = shape
850
+ @subscripts = subscripts
851
+ @negated = negated
852
+ end
853
+ def local
854
+ [@name, @binding]
855
+ end
856
+ def children
857
+ @subscripts.filter_map { |_index, offset| offset if offset.is_a?(Node) }
858
+ end
859
+ end
860
+
861
+ # `sum(w)`, `min(w)`, `max(w)`: a value worked out over a local array.
862
+ #
863
+ # `name` is the array's, not the function's, so that a read of the array's
864
+ # binding is the same question here as it is for LocalArrayRead -- the C
865
+ # name a helper is handed comes from the same place. `intrinsic` is which
866
+ # function was written.
867
+ class IntrinsicCall < Node
868
+ attr_accessor :binding
869
+ attr_reader :intrinsic, :name, :storage, :shape
870
+ def initialize (intrinsic, name, storage, shape, location = nil)
871
+ super(location)
872
+ @intrinsic = intrinsic
873
+ @name = name
874
+ @storage = storage
875
+ @shape = shape
876
+ end
877
+ def local
878
+ [@name, @binding]
879
+ end
880
+ end
881
+
882
+ # `sort(w)`: rearranges a local array where it stands. A statement and
883
+ # never a value -- what it did is in the array, which is the same bargain
884
+ # a call to a `void` function makes.
885
+ class IntrinsicStatement < Node
886
+ attr_accessor :binding
887
+ attr_reader :intrinsic, :name, :storage, :shape
888
+ def initialize (intrinsic, name, storage, shape, location = nil)
889
+ super(location)
890
+ @intrinsic = intrinsic
891
+ @name = name
892
+ @storage = storage
893
+ @shape = shape
894
+ end
895
+ def local
896
+ [@name, @binding]
897
+ end
898
+ end
899
+
900
+ # A local array standing where a C function wants a pointer.
901
+ #
902
+ # Not `ArrayAddress`, which is an operand's: that one is spelled by
903
+ # `bare_name` and is entered in `@address_arrays`, the list of arrays the
904
+ # caller packs and hands over through `data[]`. This array is already
905
+ # here, on the stack of the block that made it, so it is spelled by the
906
+ # name its declaration was written under and the caller has nothing to
907
+ # pack.
908
+ class LocalArrayAddress < Node
909
+ attr_accessor :binding
910
+ attr_reader :name, :storage, :shape
911
+ def initialize (name, storage, shape, location = nil)
912
+ super(location)
913
+ @name = name
914
+ @storage = storage
915
+ @shape = shape
916
+ end
917
+ def local
918
+ [@name, @binding]
919
+ end
920
+ end
921
+
561
922
  class KernelBody < Node
562
923
  attr_reader :statements
924
+ # The locals the kernel's block, or a function's, declares, as
925
+ # [name, binding, type], and the arrays it declares, as
926
+ # [name, binding, storage, shape].
927
+ attr_accessor :declarations, :array_declarations
563
928
  def initialize (statements)
564
929
  super(nil)
565
930
  @statements = statements
@@ -0,0 +1,182 @@
1
+ class CArray
2
+ module JIT
3
+
4
+ # Comparator networks for the lengths a local array is sorted at without a
5
+ # loop: a fixed sequence of compare-exchange pairs, the same one every
6
+ # time, with no branch and nothing to predict.
7
+ #
8
+ # A network is a list of `[i, j]` with `i < j`, applied in order. Each
9
+ # pair says "put the smaller of these two channels in i and the larger in
10
+ # j"; run them all and the channels come out ordered, whatever they held.
11
+ # That is the whole of the interface -- the generator turns each pair into
12
+ # one compare-exchange in C, and what decides which pair comes first is
13
+ # nothing but this list.
14
+ #
15
+ # ## Where these came from
16
+ #
17
+ # The sizes are the smallest known for each length, and for n <= 12 they
18
+ # are known to be optimal (Codish, Cruz-Filipe, Frank and Schneider-Kamp,
19
+ # "Twenty-Five Comparators Is Optimal When Sorting Nine Inputs (and
20
+ # Twenty-Nine for Ten)", 2014, for 9 and 10; the smaller ones are in
21
+ # Knuth, *The Art of Computer Programming*, vol. 3, section 5.3.4). For
22
+ # 13 to 16 they are the best known rather than proven optimal; the
23
+ # 16-input network with 60 comparators is Green's, given in Knuth's
24
+ # section 5.3.4 and figure 51.
25
+ #
26
+ # | n | comparators | n | comparators |
27
+ # |---|---|---|---|
28
+ # | 2 | 1 | 10 | 29 |
29
+ # | 3 | 3 | 11 | 35 |
30
+ # | 4 | 5 | 12 | 39 |
31
+ # | 5 | 9 | 13 | 45 |
32
+ # | 6 | 12 | 14 | 51 |
33
+ # | 7 | 16 | 15 | 56 |
34
+ # | 8 | 19 | 16 | 60 |
35
+ # | 9 | 25 | | |
36
+ #
37
+ # ## Why they can be trusted
38
+ #
39
+ # A table of pairs is exactly the kind of data that is wrong in one place
40
+ # and silently almost-right, so none of it is taken on the word of a
41
+ # citation. Every network here is checked by the **zero-one principle**:
42
+ # a comparator network sorts every input if and only if it sorts every
43
+ # sequence of zeros and ones. For n channels that is 2**n sequences --
44
+ # 65536 at the largest length here -- and `test/test_sorting_networks.rb`
45
+ # runs all of them for all fifteen lengths, along with the comparator
46
+ # count and the shape of each pair. A network that is wrong fails there
47
+ # rather than in somebody's median filter.
48
+ #
49
+ # ## Pruning
50
+ #
51
+ # Where one of these was derived from a longer one, it was derived by
52
+ # pruning: set the last input to positive infinity and every comparator
53
+ # touching that channel becomes a no-op on the others, so deleting them
54
+ # leaves a network that sorts the rest. 14 and 15 are the 16-input
55
+ # network pruned that way, and 9 is the 10-input one pruned. The others
56
+ # are written out.
57
+ module SortingNetworks
58
+
59
+ # The longest length that gets a network. Above it the generator emits
60
+ # an insertion sort, which is a loop: the tables would keep growing and
61
+ # the win over a loop shrinks as the length rises, there being more
62
+ # comparators than the machine has registers long before this.
63
+ LONGEST = 16
64
+
65
+ # Green's 16-input network, 60 comparators. 14 and 15 are pruned from
66
+ # it below, which is where their 51 and 56 come from.
67
+ GREEN_16 = [
68
+ [0, 1], [2, 3], [4, 5], [6, 7], [8, 9], [10, 11], [12, 13], [14, 15],
69
+ [0, 2], [4, 6], [8, 10], [12, 14], [1, 3], [5, 7], [9, 11], [13, 15],
70
+ [0, 4], [8, 12], [1, 5], [9, 13], [2, 6], [10, 14], [3, 7], [11, 15],
71
+ [0, 8], [1, 9], [2, 10], [3, 11], [4, 12], [5, 13], [6, 14], [7, 15],
72
+ [5, 10], [6, 9], [3, 12], [13, 14], [7, 11], [1, 2], [4, 8],
73
+ [1, 4], [7, 13], [2, 8], [11, 14],
74
+ [5, 6], [9, 10],
75
+ [2, 4], [11, 13], [3, 8], [7, 12],
76
+ [6, 8], [10, 12], [3, 5], [7, 9],
77
+ [3, 4], [5, 6], [7, 8], [9, 10], [11, 12],
78
+ [6, 7], [8, 9],
79
+ ].freeze
80
+
81
+ # The 10-input network, 29 comparators. 9 is pruned from it.
82
+ TEN = [
83
+ [0, 8], [1, 9], [2, 7], [3, 5], [4, 6],
84
+ [0, 2], [1, 4], [5, 8], [7, 9],
85
+ [0, 3], [2, 4], [5, 7], [6, 9],
86
+ [0, 1], [3, 6], [8, 9],
87
+ [1, 5], [2, 3], [4, 8], [6, 7],
88
+ [1, 2], [3, 5], [4, 6], [7, 8],
89
+ [2, 3], [4, 5], [6, 7],
90
+ [3, 4], [5, 6],
91
+ ].freeze
92
+
93
+ # Sets the channels at or above `channels` to positive infinity, which
94
+ # makes every comparator that touches one a no-op on the rest.
95
+ def self.prune (network, channels)
96
+ network.reject { |low, high| low >= channels || high >= channels }
97
+ end
98
+
99
+ NETWORKS = {
100
+ 2 => [[0, 1]],
101
+
102
+ 3 => [[0, 2], [0, 1], [1, 2]],
103
+
104
+ 4 => [[0, 1], [2, 3], [0, 2], [1, 3], [1, 2]],
105
+
106
+ 5 => [[0, 1], [3, 4], [2, 4], [2, 3], [1, 4],
107
+ [0, 3], [0, 2], [1, 3], [1, 2]],
108
+
109
+ 6 => [[1, 2], [4, 5], [0, 2], [3, 5], [0, 1], [3, 4],
110
+ [2, 5], [0, 3], [1, 4], [2, 4], [1, 3], [2, 3]],
111
+
112
+ 7 => [[1, 2], [3, 4], [5, 6], [0, 2], [3, 5], [4, 6],
113
+ [0, 1], [4, 5], [2, 6], [0, 4], [1, 5], [0, 3],
114
+ [2, 5], [1, 3], [2, 4], [2, 3]],
115
+
116
+ # Batcher's odd-even merge, which at this length is also the smallest.
117
+ 8 => [[0, 2], [1, 3], [4, 6], [5, 7],
118
+ [0, 4], [1, 5], [2, 6], [3, 7],
119
+ [0, 1], [2, 3], [4, 5], [6, 7],
120
+ [2, 4], [3, 5],
121
+ [1, 4], [3, 6],
122
+ [1, 2], [3, 4], [5, 6]],
123
+
124
+ 9 => prune(TEN, 9),
125
+
126
+ 10 => TEN,
127
+
128
+ 11 => [[0, 9], [1, 6], [2, 4], [3, 7], [5, 8],
129
+ [0, 1], [3, 5], [4, 10], [6, 9], [7, 8],
130
+ [1, 3], [2, 5], [4, 7], [8, 10],
131
+ [0, 4], [1, 2], [3, 7], [5, 9], [6, 8],
132
+ [0, 1], [2, 6], [4, 5], [7, 8], [9, 10],
133
+ [2, 4], [3, 6], [5, 7], [8, 9],
134
+ [1, 2], [3, 4], [5, 6], [7, 8],
135
+ [2, 3], [4, 5], [6, 7]],
136
+
137
+ 12 => [[0, 8], [1, 7], [2, 6], [3, 11], [4, 10], [5, 9],
138
+ [0, 1], [2, 5], [3, 4], [6, 9], [7, 8], [10, 11],
139
+ [0, 2], [1, 6], [5, 10], [9, 11],
140
+ [0, 3], [1, 2], [4, 6], [5, 7], [8, 11], [9, 10],
141
+ [1, 4], [3, 5], [6, 8], [7, 10],
142
+ [1, 3], [2, 5], [6, 9], [8, 10],
143
+ [2, 3], [4, 5], [6, 7], [8, 9],
144
+ [4, 6], [5, 7],
145
+ [3, 4], [5, 6], [7, 8]],
146
+
147
+ 13 => [[0, 12], [1, 10], [2, 9], [3, 7], [5, 11], [6, 8],
148
+ [1, 6], [2, 3], [4, 11], [7, 9], [8, 10],
149
+ [0, 4], [1, 2], [3, 6], [7, 8], [9, 10], [11, 12],
150
+ [4, 6], [5, 9], [8, 11], [10, 12],
151
+ [0, 5], [3, 8], [4, 7], [6, 11], [9, 10],
152
+ [0, 1], [2, 5], [6, 9], [7, 8], [10, 11],
153
+ [1, 3], [2, 4], [5, 6], [9, 10],
154
+ [1, 2], [3, 4], [5, 7], [6, 8],
155
+ [2, 3], [4, 5], [6, 7], [8, 9],
156
+ [3, 4], [5, 6]],
157
+
158
+ 14 => prune(GREEN_16, 14),
159
+
160
+ 15 => prune(GREEN_16, 15),
161
+
162
+ 16 => GREEN_16,
163
+ }.each_value(&:freeze).freeze
164
+
165
+ # The comparator count per length, which the tests assert and the docs
166
+ # quote. Derived rather than written down a second time.
167
+ SIZES = NETWORKS.transform_values(&:size).freeze
168
+
169
+ # The network for `length`, or nil where there is none -- length 1 is
170
+ # already sorted and needs no comparator, and anything above LONGEST
171
+ # gets a loop instead.
172
+ def self.for (length)
173
+ NETWORKS[length]
174
+ end
175
+
176
+ def self.covers? (length)
177
+ length == 1 || NETWORKS.key?(length)
178
+ end
179
+
180
+ end
181
+ end
182
+ end