permutation.go raw
1 // Package permutation implements S_3, the symmetric group on the three
2 // trigram axes: Bonding (bit 0), Constraint (bit 1), Energy (bit 2).
3 //
4 // Each permutation defines an alternative projection angle through which
5 // hexagram semantics can be interpreted. There are exactly 6 elements:
6 // the identity, three transpositions, and two 3-cycles. This is the
7 // minimal symmetry group of the 3-bit lattice encoding.
8 //
9 // 3 bits, 3! = 6 permutations. Simple permutation.
10 package permutation
11
12 import "git.mleku.dev/mleku/dendrite/pkg/state"
13
14 // Perm is one of the 6 elements of S_3. It specifies how the 3 trigram
15 // bit positions are remapped. Value range: 0-5.
16 type Perm uint8
17
18 const (
19 Identity Perm = 0 // (B,C,E) -> (B,C,E)
20 Swap01 Perm = 1 // (B,C,E) -> (C,B,E) — swap bonding and constraint
21 Swap02 Perm = 2 // (B,C,E) -> (E,C,B) — swap bonding and energy
22 Swap12 Perm = 3 // (B,C,E) -> (B,E,C) — swap constraint and energy
23 Cycle012 Perm = 4 // (B,C,E) -> (C,E,B) — 3-cycle: 0->1->2->0
24 Cycle021 Perm = 5 // (B,C,E) -> (E,B,C) — 3-cycle: 0->2->1->0
25 )
26
27 // Count is the order of S_3.
28 const Count = 6
29
30 // mapping[p] = [dest_of_bit0, dest_of_bit1, dest_of_bit2].
31 // For permutation p, source bit i goes to position mapping[p][i].
32 var mapping = [Count][3]uint8{
33 {0, 1, 2}, // Identity
34 {1, 0, 2}, // Swap01: 0<->1
35 {2, 1, 0}, // Swap02: 0<->2
36 {0, 2, 1}, // Swap12: 1<->2
37 {1, 2, 0}, // Cycle012: 0->1, 1->2, 2->0
38 {2, 0, 1}, // Cycle021: 0->2, 1->0, 2->1
39 }
40
41 // inverse[p] is p^-1. Transpositions are self-inverse; 3-cycles swap.
42 var inverse = [Count]Perm{
43 Identity, // Identity^-1 = Identity
44 Swap01, // (01)^-1 = (01)
45 Swap02, // (02)^-1 = (02)
46 Swap12, // (12)^-1 = (12)
47 Cycle021, // (012)^-1 = (021)
48 Cycle012, // (021)^-1 = (012)
49 }
50
51 // compose[a][b] = a . b (apply b first, then a).
52 // Full Cayley table for S_3, computed from result[i] = a.mapping[b.mapping[i]].
53 var compose = [Count][Count]Perm{
54 // Id 01 02 12 012 021
55 /* Id */ {Identity, Swap01, Swap02, Swap12, Cycle012, Cycle021},
56 /* 01 */ {Swap01, Identity, Cycle021, Cycle012, Swap12, Swap02},
57 /* 02 */ {Swap02, Cycle012, Identity, Cycle021, Swap01, Swap12},
58 /* 12 */ {Swap12, Cycle021, Cycle012, Identity, Swap02, Swap01},
59 /* 012 */ {Cycle012, Swap02, Swap12, Swap01, Cycle021, Identity},
60 /* 021 */ {Cycle021, Swap12, Swap01, Swap02, Identity, Cycle012},
61 }
62
63 // All returns all 6 permutations in canonical order.
64 func All() [Count]Perm {
65 return [Count]Perm{Identity, Swap01, Swap02, Swap12, Cycle012, Cycle021}
66 }
67
68 // Inverse returns p^-1.
69 func (p Perm) Inverse() Perm {
70 return inverse[p]
71 }
72
73 // Compose returns the permutation that applies q first, then p.
74 // (p.Compose(q)).ApplyTrigram(t) == p.ApplyTrigram(q.ApplyTrigram(t))
75 func (p Perm) Compose(q Perm) Perm {
76 return compose[p][q]
77 }
78
79 // ApplyTrigram permutes the 3 bits of a trigram according to this
80 // permutation. Source bit i moves to position mapping[p][i].
81 func (p Perm) ApplyTrigram(t state.Trigram) state.Trigram {
82 if p == Identity {
83 return t
84 }
85 m := mapping[p]
86 var result uint8
87 for src := range uint8(3) {
88 if uint8(t)&(1<<src) != 0 {
89 result |= 1 << m[src]
90 }
91 }
92 return state.Trigram(result)
93 }
94
95 // ApplyHexagram permutes both inner and outer trigrams of a hexagram.
96 func (p Perm) ApplyHexagram(h state.Hexagram) state.Hexagram {
97 if p == Identity {
98 return h
99 }
100 return state.Hex(p.ApplyTrigram(h.Inner()), p.ApplyTrigram(h.Outer()))
101 }
102
103 // String returns a human-readable name for the permutation.
104 func (p Perm) String() string {
105 switch p {
106 case Identity:
107 return "Identity"
108 case Swap01:
109 return "Swap(B,C)"
110 case Swap02:
111 return "Swap(B,E)"
112 case Swap12:
113 return "Swap(C,E)"
114 case Cycle012:
115 return "Cycle(B->C->E)"
116 case Cycle021:
117 return "Cycle(B->E->C)"
118 default:
119 return "Invalid"
120 }
121 }
122