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