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