// Package permutation implements S_3, the symmetric group on the three // trigram axes: Bonding (bit 0), Constraint (bit 1), Energy (bit 2). // // Each permutation defines an alternative projection angle through which // hexagram semantics can be interpreted. There are exactly 6 elements: // the identity, three transpositions, and two 3-cycles. This is the // minimal symmetry group of the 3-bit lattice encoding. // // 3 bits, 3! = 6 permutations. Simple permutation. package permutation import "git.mleku.dev/mleku/dendrite/pkg/state" // Perm is one of the 6 elements of S_3. It specifies how the 3 trigram // bit positions are remapped. Value range: 0-5. type Perm uint8 const ( Identity Perm = 0 // (B,C,E) -> (B,C,E) Swap01 Perm = 1 // (B,C,E) -> (C,B,E) — swap bonding and constraint Swap02 Perm = 2 // (B,C,E) -> (E,C,B) — swap bonding and energy Swap12 Perm = 3 // (B,C,E) -> (B,E,C) — swap constraint and energy Cycle012 Perm = 4 // (B,C,E) -> (C,E,B) — 3-cycle: 0->1->2->0 Cycle021 Perm = 5 // (B,C,E) -> (E,B,C) — 3-cycle: 0->2->1->0 ) // Count is the order of S_3. const Count = 6 // mapping[p] = [dest_of_bit0, dest_of_bit1, dest_of_bit2]. // For permutation p, source bit i goes to position mapping[p][i]. var mapping = [Count][3]uint8{ {0, 1, 2}, // Identity {1, 0, 2}, // Swap01: 0<->1 {2, 1, 0}, // Swap02: 0<->2 {0, 2, 1}, // Swap12: 1<->2 {1, 2, 0}, // Cycle012: 0->1, 1->2, 2->0 {2, 0, 1}, // Cycle021: 0->2, 1->0, 2->1 } // inverse[p] is p^-1. Transpositions are self-inverse; 3-cycles swap. var inverse = [Count]Perm{ Identity, // Identity^-1 = Identity Swap01, // (01)^-1 = (01) Swap02, // (02)^-1 = (02) Swap12, // (12)^-1 = (12) Cycle021, // (012)^-1 = (021) Cycle012, // (021)^-1 = (012) } // compose[a][b] = a . b (apply b first, then a). // Full Cayley table for S_3, computed from result[i] = a.mapping[b.mapping[i]]. var compose = [Count][Count]Perm{ // Id 01 02 12 012 021 /* Id */ {Identity, Swap01, Swap02, Swap12, Cycle012, Cycle021}, /* 01 */ {Swap01, Identity, Cycle021, Cycle012, Swap12, Swap02}, /* 02 */ {Swap02, Cycle012, Identity, Cycle021, Swap01, Swap12}, /* 12 */ {Swap12, Cycle021, Cycle012, Identity, Swap02, Swap01}, /* 012 */ {Cycle012, Swap02, Swap12, Swap01, Cycle021, Identity}, /* 021 */ {Cycle021, Swap12, Swap01, Swap02, Identity, Cycle012}, } // All returns all 6 permutations in canonical order. func All() [Count]Perm { return [Count]Perm{Identity, Swap01, Swap02, Swap12, Cycle012, Cycle021} } // Inverse returns p^-1. func (p Perm) Inverse() Perm { return inverse[p] } // Compose returns the permutation that applies q first, then p. // (p.Compose(q)).ApplyTrigram(t) == p.ApplyTrigram(q.ApplyTrigram(t)) func (p Perm) Compose(q Perm) Perm { return compose[p][q] } // ApplyTrigram permutes the 3 bits of a trigram according to this // permutation. Source bit i moves to position mapping[p][i]. func (p Perm) ApplyTrigram(t state.Trigram) state.Trigram { if p == Identity { return t } m := mapping[p] var result uint8 for src := range uint8(3) { if uint8(t)&(1<C->E)" case Cycle021: return "Cycle(B->E->C)" default: return "Invalid" } }