carray-jit 0.1.2 → 0.1.3

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +771 -3
  3. data/README.md +7 -6
  4. data/carray-jit.gemspec +1 -3
  5. data/docs/00_Introduction.md +4 -3
  6. data/docs/01_GettingStarted.md +1 -1
  7. data/docs/02_KernelShapes.md +93 -14
  8. data/docs/03_SupportedFeatures.md +582 -26
  9. data/docs/04_Compiling.md +33 -6
  10. data/docs/05_DesignNotes.md +3 -3
  11. data/docs/06_Cheatsheet.md +198 -5
  12. data/docs/07_StepByStep.ja.md +534 -0
  13. data/docs/07_StepByStep.md +535 -0
  14. data/examples/README.md +12 -0
  15. data/examples/applications/alarm.rb +121 -0
  16. data/examples/applications/collatz.rb +105 -0
  17. data/examples/applications/cubic_spline.rb +331 -0
  18. data/examples/applications/dithering.rb +144 -0
  19. data/examples/applications/group_stats.rb +115 -0
  20. data/examples/applications/lookup.rb +126 -0
  21. data/examples/applications/median_filter.rb +153 -0
  22. data/examples/applications/parcel_ascent.rb +220 -0
  23. data/examples/applications/point_in_polygon.rb +111 -0
  24. data/examples/applications/random_walk.rb +98 -0
  25. data/examples/applications/van_der_pol.rb +186 -0
  26. data/examples/applications/wet_bulb.rb +140 -0
  27. data/examples/features/10_complex.rb +14 -4
  28. data/examples/features/15_loops.rb +7 -1
  29. data/lib/carray/jit/access.rb +14 -0
  30. data/lib/carray/jit/analyzer.rb +2077 -136
  31. data/lib/carray/jit/block_reader.rb +37 -6
  32. data/lib/carray/jit/c_function.rb +613 -76
  33. data/lib/carray/jit/c_generator.rb +1595 -156
  34. data/lib/carray/jit/call.rb +68 -0
  35. data/lib/carray/jit/compiler.rb +75 -11
  36. data/lib/carray/jit/kernel.rb +369 -32
  37. data/lib/carray/jit/node.rb +359 -9
  38. data/lib/carray/jit/sorting_networks.rb +182 -0
  39. data/lib/carray/jit/type_assignment.rb +371 -34
  40. data/lib/carray/jit/version.rb +1 -1
  41. data/lib/carray/jit.rb +560 -64
  42. metadata +22 -8
  43. data/ext/carray_jit_access/carray_jit_access.c +0 -460
  44. 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,40 @@ 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
+
113
156
  # Zero of whatever type another expression has.
114
157
  #
115
158
  # A sum has to start from a zero of the summand's type: start it from an
@@ -145,12 +188,21 @@ class CArray
145
188
  # Nothing is written through it: a kernel writes the cell its outer
146
189
  # indices are on, and the inner loop is what runs within that cell.
147
190
  class InnerLoop < Node
148
- attr_reader :index, :from, :to, :statements
149
- def initialize (index, from, to, statements, location = nil)
191
+ # `step` is the stride the loop counts by, and `to` is exclusive of it:
192
+ # a descending loop ends one below the last index it visits, as an
193
+ # extent written with `step` does.
194
+ attr_reader :index, :from, :to, :step, :statements
195
+ # The locals its block declares, as [name, binding, type], the arrays it
196
+ # declares, as [name, binding, storage, shape], and the ones that were
197
+ # live when the loop was entered, by name as [type, binding] -- all
198
+ # settled by the typing.
199
+ attr_accessor :declarations, :array_declarations, :entering
200
+ def initialize (index, from, to, statements, location = nil, step = 1)
150
201
  super(location)
151
202
  @index = index
152
203
  @from = from
153
204
  @to = to
205
+ @step = step
154
206
  @statements = statements
155
207
  end
156
208
  def children
@@ -333,6 +385,41 @@ class CArray
333
385
  end
334
386
  end
335
387
 
388
+ # A draw from a captured CArray::Rng.
389
+ #
390
+ # The generator is not a function this kernel closed over -- nothing of
391
+ # it is compiled here. Its C arrives as text from CArray, is pasted into
392
+ # the preamble beside the helpers this compiler writes itself, and the
393
+ # draw is a call to it. So the sequence a kernel gets is the sequence
394
+ # `CArray#random!` gets, because it is the same code advancing the same
395
+ # cells and not a second implementation agreeing with the first.
396
+ #
397
+ # The state is an ordinary captured array under a name this compiler made
398
+ # up, handed over as an address the way any array a C function writes
399
+ # through is. That is what carries the sequence across the call: the
400
+ # cells the kernel advanced are the cells the generator still holds when
401
+ # it comes back.
402
+ class RandomDraw < Node
403
+ # The name the block reached the generator by, for a message to say.
404
+ attr_reader :generator
405
+ # The made-up name of its state array.
406
+ attr_reader :state
407
+ # What this draw answers: `:rand` a double in [0.0, 1.0), `:bits` the
408
+ # raw word the generator produced. Both advance the same state by one
409
+ # draw -- one step of the generator read two ways, not two sequences --
410
+ # so a kernel mixing them is still walking the one sequence.
411
+ attr_reader :kind
412
+ def initialize (generator, state, kind, location = nil)
413
+ super(location)
414
+ @generator = generator
415
+ @state = state
416
+ @kind = kind
417
+ end
418
+ def children
419
+ []
420
+ end
421
+ end
422
+
336
423
  # `p[i]` where `p` is a pointer parameter of a compiled function.
337
424
  #
338
425
  # Not an ElementRead: an array's cell is reached through a base and a
@@ -524,18 +611,49 @@ class CArray
524
611
  end
525
612
 
526
613
  class Assignment < Node
527
- attr_accessor :binding_name
528
- attr_reader :name, :expression
529
- def initialize (name, expression, location = nil)
614
+ # Which of the local's variables this writes, as LocalRead#binding.
615
+ attr_accessor :binding
616
+ # Which scope the local belongs to, counted out from the kernel's block
617
+ # -- or a function's -- at 0, one more for each inner loop's block.
618
+ attr_reader :name, :expression, :scope
619
+ def initialize (name, expression, location = nil, scope = 0)
530
620
  super(location)
531
621
  @name = name
532
622
  @expression = expression
623
+ @scope = scope
624
+ end
625
+ def local
626
+ [@name, @binding]
533
627
  end
534
628
  def children
535
629
  [@expression]
536
630
  end
537
631
  end
538
632
 
633
+ # `prev, cur = cur, prev + cur`
634
+ #
635
+ # Ruby settles every value on the right before it writes any of them, so
636
+ # the temporaries below are the statement rather than a way of writing
637
+ # it: without them the line above would write `prev` and then read the
638
+ # new one back while working out `cur`, which is the one thing the
639
+ # spelling exists to prevent.
640
+ #
641
+ # Held as the statements it became -- one assignment to a name of its own
642
+ # per value, in the order they were written, and then the writes. So
643
+ # everything downstream reads ordinary assignments and writes: the types,
644
+ # the masks a local carries, the C names, the order the body is swept in.
645
+ # Nothing here has to know that a parallel assignment is what they were.
646
+ class ParallelAssignment < Node
647
+ attr_reader :statements
648
+ def initialize (statements, location = nil)
649
+ super(location)
650
+ @statements = statements
651
+ end
652
+ def children
653
+ @statements
654
+ end
655
+ end
656
+
539
657
  # A cell of an array: out[i, j] = ...
540
658
  #
541
659
  # Writes are always at the cell the loop is on. Writing elsewhere would
@@ -558,8 +676,240 @@ class CArray
558
676
  end
559
677
  end
560
678
 
679
+ # `w = CArray.double(9)`, `CArray.new(:int64, [256])`, or
680
+ # `CArray.empty(:float64, [9])`: an array the body owns.
681
+ #
682
+ # It is declared at the head of the C block its Ruby scope stands for, as
683
+ # a local is, and lives on that block's stack. The zeroed spellings are
684
+ # cleared where the statement stands -- in Ruby a fresh array is made
685
+ # there each time the line runs, and the clearing is what says the same
686
+ # thing in C. `empty` says nothing there, and its cells are whatever the
687
+ # stack held.
688
+ #
689
+ # Not an ElementWrite or an Assignment. Those are tied to the operand
690
+ # tables -- the line-up, the dispatcher, the extents, the masks, the box a
691
+ # view is transferred in -- and this array is in none of them: the block
692
+ # wrote its shape, so its strides are constants and its bounds are settled
693
+ # either where the block is read or at the access. `PointerRead` stands
694
+ # apart for the same reason.
695
+ class LocalArrayDeclaration < Node
696
+ # Which of the name's variables this declares, as LocalRead#binding.
697
+ attr_accessor :binding
698
+ # `zeroed` is false for the `empty` spelling.
699
+ # `scope` is counted the way Assignment#scope is.
700
+ # `heap` is true where the array does not go on the stack: its shape is
701
+ # larger than a frame should hold, or is not known until the kernel
702
+ # runs. Then it is one allocation at the kernel's entry and a free at
703
+ # its exit, and the name is a pointer rather than an automatic array --
704
+ # which changes the declaration and nothing about how a cell is
705
+ # reached.
706
+ attr_reader :name, :storage, :shape, :zeroed, :scope, :heap
707
+ def initialize (name, storage, shape, zeroed, location = nil, scope = 0,
708
+ heap = false)
709
+ super(location)
710
+ @name = name
711
+ @storage = storage
712
+ @shape = shape
713
+ @zeroed = zeroed
714
+ @scope = scope
715
+ @heap = heap
716
+ end
717
+ def local
718
+ [@name, @binding]
719
+ end
720
+ # The shape's axes, as Nodes, for whatever has to walk them: an extent
721
+ # the block wrote as an expression is an expression like any other, and
722
+ # is typed and checked as one.
723
+ def children
724
+ @shape.filter_map { |extent|
725
+ extent.node if extent.is_a?(LocalArrayExtent)
726
+ }
727
+ end
728
+ end
729
+
730
+ # `w[k]`, `w[j + 1]`, `w[counts[i]]`.
731
+ #
732
+ # `subscripts` holds one [index, offset] pair per axis, the shape
733
+ # ElementRead's take: an index with a literal offset where the reach was
734
+ # settled as the block was read, or [nil, expression] where the position
735
+ # is one only the running kernel knows and is checked there.
736
+ class LocalArrayRead < Node
737
+ attr_accessor :binding
738
+ attr_reader :name, :storage, :shape, :subscripts
739
+ def initialize (name, storage, shape, subscripts, location = nil)
740
+ super(location)
741
+ @name = name
742
+ @storage = storage
743
+ @shape = shape
744
+ @subscripts = subscripts
745
+ end
746
+ def local
747
+ [@name, @binding]
748
+ end
749
+ def children
750
+ @subscripts.filter_map { |_index, offset| offset if offset.is_a?(Node) }
751
+ end
752
+ end
753
+
754
+ class LocalArrayWrite < Node
755
+ attr_accessor :binding
756
+ attr_reader :name, :storage, :shape, :subscripts, :expression
757
+ def initialize (name, storage, shape, subscripts, expression,
758
+ location = nil)
759
+ super(location)
760
+ @name = name
761
+ @storage = storage
762
+ @shape = shape
763
+ @subscripts = subscripts
764
+ @expression = expression
765
+ end
766
+ def local
767
+ [@name, @binding]
768
+ end
769
+ def children
770
+ [@expression] +
771
+ @subscripts.filter_map { |_index, offset| offset if offset.is_a?(Node) }
772
+ end
773
+ end
774
+
775
+ # One axis of a local array whose length the block wrote as an
776
+ # expression over captured integers rather than as a number.
777
+ #
778
+ # Not a Node: it stands in a shape, where every other entry is an
779
+ # Integer, and what a shape is asked is how long an axis is -- not what
780
+ # type it computes in. `text` is what the block wrote, which is what
781
+ # makes two of these the same extent: the expression is worked out at the
782
+ # kernel's entry, so two declarations that wrote the same thing are one C
783
+ # array, exactly as two that wrote the same number are.
784
+ class LocalArrayExtent
785
+ attr_reader :text, :node
786
+ def initialize (text, node)
787
+ @text = text
788
+ @node = node
789
+ end
790
+ def == (other)
791
+ other.is_a?(LocalArrayExtent) && other.text == @text
792
+ end
793
+ alias eql? ==
794
+ def hash
795
+ @text.hash
796
+ end
797
+ def to_s
798
+ @text
799
+ end
800
+ end
801
+
802
+ # `w[k] = UNDEF`: marks the cell missing and leaves its bytes alone.
803
+ #
804
+ # Beside LocalArrayWrite for the reason MaskWrite stands beside
805
+ # ElementWrite: UNDEF is a mark rather than a value, so there is no
806
+ # expression here to compute or to store.
807
+ class LocalArrayMaskWrite < Node
808
+ attr_accessor :binding
809
+ attr_reader :name, :storage, :shape, :subscripts
810
+ def initialize (name, storage, shape, subscripts, location = nil)
811
+ super(location)
812
+ @name = name
813
+ @storage = storage
814
+ @shape = shape
815
+ @subscripts = subscripts
816
+ end
817
+ def local
818
+ [@name, @binding]
819
+ end
820
+ def children
821
+ @subscripts.filter_map { |_index, offset| offset if offset.is_a?(Node) }
822
+ end
823
+ end
824
+
825
+ # `w[k] == UNDEF`, and `!=` with `negated`: reads the shadow byte beside
826
+ # the cell rather than the cell.
827
+ class LocalArrayMaskTest < Node
828
+ attr_accessor :binding
829
+ attr_reader :name, :storage, :shape, :subscripts, :negated
830
+ def initialize (name, storage, shape, subscripts, negated, location = nil)
831
+ super(location)
832
+ @name = name
833
+ @storage = storage
834
+ @shape = shape
835
+ @subscripts = subscripts
836
+ @negated = negated
837
+ end
838
+ def local
839
+ [@name, @binding]
840
+ end
841
+ def children
842
+ @subscripts.filter_map { |_index, offset| offset if offset.is_a?(Node) }
843
+ end
844
+ end
845
+
846
+ # `sum(w)`, `min(w)`, `max(w)`: a value worked out over a local array.
847
+ #
848
+ # `name` is the array's, not the function's, so that a read of the array's
849
+ # binding is the same question here as it is for LocalArrayRead -- the C
850
+ # name a helper is handed comes from the same place. `intrinsic` is which
851
+ # function was written.
852
+ class IntrinsicCall < Node
853
+ attr_accessor :binding
854
+ attr_reader :intrinsic, :name, :storage, :shape
855
+ def initialize (intrinsic, name, storage, shape, location = nil)
856
+ super(location)
857
+ @intrinsic = intrinsic
858
+ @name = name
859
+ @storage = storage
860
+ @shape = shape
861
+ end
862
+ def local
863
+ [@name, @binding]
864
+ end
865
+ end
866
+
867
+ # `sort(w)`: rearranges a local array where it stands. A statement and
868
+ # never a value -- what it did is in the array, which is the same bargain
869
+ # a call to a `void` function makes.
870
+ class IntrinsicStatement < Node
871
+ attr_accessor :binding
872
+ attr_reader :intrinsic, :name, :storage, :shape
873
+ def initialize (intrinsic, name, storage, shape, location = nil)
874
+ super(location)
875
+ @intrinsic = intrinsic
876
+ @name = name
877
+ @storage = storage
878
+ @shape = shape
879
+ end
880
+ def local
881
+ [@name, @binding]
882
+ end
883
+ end
884
+
885
+ # A local array standing where a C function wants a pointer.
886
+ #
887
+ # Not `ArrayAddress`, which is an operand's: that one is spelled by
888
+ # `bare_name` and is entered in `@address_arrays`, the list of arrays the
889
+ # caller packs and hands over through `data[]`. This array is already
890
+ # here, on the stack of the block that made it, so it is spelled by the
891
+ # name its declaration was written under and the caller has nothing to
892
+ # pack.
893
+ class LocalArrayAddress < Node
894
+ attr_accessor :binding
895
+ attr_reader :name, :storage, :shape
896
+ def initialize (name, storage, shape, location = nil)
897
+ super(location)
898
+ @name = name
899
+ @storage = storage
900
+ @shape = shape
901
+ end
902
+ def local
903
+ [@name, @binding]
904
+ end
905
+ end
906
+
561
907
  class KernelBody < Node
562
908
  attr_reader :statements
909
+ # The locals the kernel's block, or a function's, declares, as
910
+ # [name, binding, type], and the arrays it declares, as
911
+ # [name, binding, storage, shape].
912
+ attr_accessor :declarations, :array_declarations
563
913
  def initialize (statements)
564
914
  super(nil)
565
915
  @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