permutation_test.go raw
1 package permutation
2
3 import (
4 "testing"
5
6 "git.mleku.dev/mleku/dendrite/pkg/state"
7 )
8
9 func TestAllDistinct(t *testing.T) {
10 all := All()
11 for i := range all {
12 for j := i + 1; j < len(all); j++ {
13 if all[i] == all[j] {
14 t.Errorf("Perm %d and %d are identical: %v", i, j, all[i])
15 }
16 }
17 }
18 }
19
20 func TestIdentityIsIdentity(t *testing.T) {
21 for tri := range uint8(8) {
22 got := Identity.ApplyTrigram(state.Trigram(tri))
23 if got != state.Trigram(tri) {
24 t.Errorf("Identity.ApplyTrigram(%03b) = %03b, want %03b", tri, got, tri)
25 }
26 }
27 for h := range uint8(64) {
28 got := Identity.ApplyHexagram(state.Hexagram(h))
29 if got != state.Hexagram(h) {
30 t.Errorf("Identity.ApplyHexagram(%d) = %d, want %d", h, got, h)
31 }
32 }
33 }
34
35 func TestInverseProperty(t *testing.T) {
36 for _, p := range All() {
37 product := p.Compose(p.Inverse())
38 if product != Identity {
39 t.Errorf("%v.Compose(%v.Inverse()) = %v, want Identity", p, p, product)
40 }
41 product = p.Inverse().Compose(p)
42 if product != Identity {
43 t.Errorf("%v.Inverse().Compose(%v) = %v, want Identity", p, p, product)
44 }
45 }
46 }
47
48 func TestCompositionAssociativity(t *testing.T) {
49 all := All()
50 for _, a := range all {
51 for _, b := range all {
52 for _, c := range all {
53 ab_c := a.Compose(b).Compose(c)
54 a_bc := a.Compose(b.Compose(c))
55 if ab_c != a_bc {
56 t.Errorf("(%v.%v).%v = %v but %v.(%v.%v) = %v",
57 a, b, c, ab_c, a, b, c, a_bc)
58 }
59 }
60 }
61 }
62 }
63
64 func TestCayleyTableClosure(t *testing.T) {
65 all := All()
66 valid := map[Perm]bool{}
67 for _, p := range all {
68 valid[p] = true
69 }
70 for _, a := range all {
71 for _, b := range all {
72 product := a.Compose(b)
73 if !valid[product] {
74 t.Errorf("%v.Compose(%v) = %d, not a valid Perm", a, b, product)
75 }
76 }
77 }
78 }
79
80 func TestApplyTrigramAllPermsTrigrams(t *testing.T) {
81 // Verify that each permutation produces 8 distinct outputs
82 // (it's a bijection on the 8 trigrams).
83 for _, p := range All() {
84 seen := map[state.Trigram]bool{}
85 for tri := range uint8(8) {
86 result := p.ApplyTrigram(state.Trigram(tri))
87 if result > 7 {
88 t.Errorf("%v.ApplyTrigram(%03b) = %03b, out of range", p, tri, result)
89 }
90 seen[result] = true
91 }
92 if len(seen) != 8 {
93 t.Errorf("%v is not a bijection on trigrams: produced %d distinct values", p, len(seen))
94 }
95 }
96 }
97
98 func TestApplyTrigramSpecificCases(t *testing.T) {
99 tests := []struct {
100 perm Perm
101 in state.Trigram
102 want state.Trigram
103 }{
104 // Swap01: bit0 <-> bit1
105 {Swap01, state.Thunder, state.Water}, // 001 -> 010
106 {Swap01, state.Water, state.Thunder}, // 010 -> 001
107 {Swap01, state.Heaven, state.Wind}, // 101 -> 110
108 {Swap01, state.Mountain, state.Mountain}, // 111 -> 111
109
110 // Swap02: bit0 <-> bit2
111 {Swap02, state.Thunder, state.Fire}, // 001 -> 100
112 {Swap02, state.Fire, state.Thunder}, // 100 -> 001
113 {Swap02, state.Lake, state.Wind}, // 011 -> 110
114
115 // Swap12: bit1 <-> bit2
116 {Swap12, state.Water, state.Fire}, // 010 -> 100
117 {Swap12, state.Fire, state.Water}, // 100 -> 010
118 {Swap12, state.Lake, state.Heaven}, // 011 -> 101
119
120 // Cycle012: 0->1, 1->2, 2->0
121 {Cycle012, state.Thunder, state.Water}, // 001 -> 010
122 {Cycle012, state.Water, state.Fire}, // 010 -> 100
123 {Cycle012, state.Fire, state.Thunder}, // 100 -> 001
124
125 // Cycle021: 0->2, 1->0, 2->1
126 {Cycle021, state.Thunder, state.Fire}, // 001 -> 100
127 {Cycle021, state.Water, state.Thunder}, // 010 -> 001
128 {Cycle021, state.Fire, state.Water}, // 100 -> 010
129 }
130 for _, tt := range tests {
131 got := tt.perm.ApplyTrigram(tt.in)
132 if got != tt.want {
133 t.Errorf("%v.ApplyTrigram(%03b) = %03b, want %03b",
134 tt.perm, uint8(tt.in), uint8(got), uint8(tt.want))
135 }
136 }
137 }
138
139 func TestApplyHexagramConsistency(t *testing.T) {
140 // ApplyHexagram should be equivalent to applying to inner and outer independently.
141 for _, p := range All() {
142 for h := range uint8(64) {
143 hex := state.Hexagram(h)
144 got := p.ApplyHexagram(hex)
145 want := state.Hex(p.ApplyTrigram(hex.Inner()), p.ApplyTrigram(hex.Outer()))
146 if got != want {
147 t.Errorf("%v.ApplyHexagram(%d): got %d, want %d", p, h, got, want)
148 }
149 }
150 }
151 }
152
153 func TestComposeSemantics(t *testing.T) {
154 // p.Compose(q).Apply(t) == p.Apply(q.Apply(t))
155 for _, p := range All() {
156 for _, q := range All() {
157 pq := p.Compose(q)
158 for tri := range uint8(8) {
159 t_in := state.Trigram(tri)
160 got := pq.ApplyTrigram(t_in)
161 want := p.ApplyTrigram(q.ApplyTrigram(t_in))
162 if got != want {
163 t.Errorf("(%v.%v).Apply(%03b) = %03b, but %v.Apply(%v.Apply(%03b)) = %03b",
164 p, q, tri, got, p, q, tri, want)
165 }
166 }
167 }
168 }
169 }
170
171 func TestString(t *testing.T) {
172 for _, p := range All() {
173 s := p.String()
174 if s == "Invalid" {
175 t.Errorf("Perm %d has no name", p)
176 }
177 if s == "" {
178 t.Errorf("Perm %d has empty string", p)
179 }
180 }
181 if Perm(99).String() != "Invalid" {
182 t.Error("out-of-range Perm should be Invalid")
183 }
184 }
185