package permutation import ( "testing" "git.mleku.dev/mleku/dendrite/pkg/state" ) func TestAllDistinct(t *testing.T) { all := All() for i := range all { for j := i + 1; j < len(all); j++ { if all[i] == all[j] { t.Errorf("Perm %d and %d are identical: %v", i, j, all[i]) } } } } func TestIdentityIsIdentity(t *testing.T) { for tri := range uint8(8) { got := Identity.ApplyTrigram(state.Trigram(tri)) if got != state.Trigram(tri) { t.Errorf("Identity.ApplyTrigram(%03b) = %03b, want %03b", tri, got, tri) } } for h := range uint8(64) { got := Identity.ApplyHexagram(state.Hexagram(h)) if got != state.Hexagram(h) { t.Errorf("Identity.ApplyHexagram(%d) = %d, want %d", h, got, h) } } } func TestInverseProperty(t *testing.T) { for _, p := range All() { product := p.Compose(p.Inverse()) if product != Identity { t.Errorf("%v.Compose(%v.Inverse()) = %v, want Identity", p, p, product) } product = p.Inverse().Compose(p) if product != Identity { t.Errorf("%v.Inverse().Compose(%v) = %v, want Identity", p, p, product) } } } func TestCompositionAssociativity(t *testing.T) { all := All() for _, a := range all { for _, b := range all { for _, c := range all { ab_c := a.Compose(b).Compose(c) a_bc := a.Compose(b.Compose(c)) if ab_c != a_bc { t.Errorf("(%v.%v).%v = %v but %v.(%v.%v) = %v", a, b, c, ab_c, a, b, c, a_bc) } } } } } func TestCayleyTableClosure(t *testing.T) { all := All() valid := map[Perm]bool{} for _, p := range all { valid[p] = true } for _, a := range all { for _, b := range all { product := a.Compose(b) if !valid[product] { t.Errorf("%v.Compose(%v) = %d, not a valid Perm", a, b, product) } } } } func TestApplyTrigramAllPermsTrigrams(t *testing.T) { // Verify that each permutation produces 8 distinct outputs // (it's a bijection on the 8 trigrams). for _, p := range All() { seen := map[state.Trigram]bool{} for tri := range uint8(8) { result := p.ApplyTrigram(state.Trigram(tri)) if result > 7 { t.Errorf("%v.ApplyTrigram(%03b) = %03b, out of range", p, tri, result) } seen[result] = true } if len(seen) != 8 { t.Errorf("%v is not a bijection on trigrams: produced %d distinct values", p, len(seen)) } } } func TestApplyTrigramSpecificCases(t *testing.T) { tests := []struct { perm Perm in state.Trigram want state.Trigram }{ // Swap01: bit0 <-> bit1 {Swap01, state.Thunder, state.Water}, // 001 -> 010 {Swap01, state.Water, state.Thunder}, // 010 -> 001 {Swap01, state.Heaven, state.Wind}, // 101 -> 110 {Swap01, state.Mountain, state.Mountain}, // 111 -> 111 // Swap02: bit0 <-> bit2 {Swap02, state.Thunder, state.Fire}, // 001 -> 100 {Swap02, state.Fire, state.Thunder}, // 100 -> 001 {Swap02, state.Lake, state.Wind}, // 011 -> 110 // Swap12: bit1 <-> bit2 {Swap12, state.Water, state.Fire}, // 010 -> 100 {Swap12, state.Fire, state.Water}, // 100 -> 010 {Swap12, state.Lake, state.Heaven}, // 011 -> 101 // Cycle012: 0->1, 1->2, 2->0 {Cycle012, state.Thunder, state.Water}, // 001 -> 010 {Cycle012, state.Water, state.Fire}, // 010 -> 100 {Cycle012, state.Fire, state.Thunder}, // 100 -> 001 // Cycle021: 0->2, 1->0, 2->1 {Cycle021, state.Thunder, state.Fire}, // 001 -> 100 {Cycle021, state.Water, state.Thunder}, // 010 -> 001 {Cycle021, state.Fire, state.Water}, // 100 -> 010 } for _, tt := range tests { got := tt.perm.ApplyTrigram(tt.in) if got != tt.want { t.Errorf("%v.ApplyTrigram(%03b) = %03b, want %03b", tt.perm, uint8(tt.in), uint8(got), uint8(tt.want)) } } } func TestApplyHexagramConsistency(t *testing.T) { // ApplyHexagram should be equivalent to applying to inner and outer independently. for _, p := range All() { for h := range uint8(64) { hex := state.Hexagram(h) got := p.ApplyHexagram(hex) want := state.Hex(p.ApplyTrigram(hex.Inner()), p.ApplyTrigram(hex.Outer())) if got != want { t.Errorf("%v.ApplyHexagram(%d): got %d, want %d", p, h, got, want) } } } } func TestComposeSemantics(t *testing.T) { // p.Compose(q).Apply(t) == p.Apply(q.Apply(t)) for _, p := range All() { for _, q := range All() { pq := p.Compose(q) for tri := range uint8(8) { t_in := state.Trigram(tri) got := pq.ApplyTrigram(t_in) want := p.ApplyTrigram(q.ApplyTrigram(t_in)) if got != want { t.Errorf("(%v.%v).Apply(%03b) = %03b, but %v.Apply(%v.Apply(%03b)) = %03b", p, q, tri, got, p, q, tri, want) } } } } } func TestString(t *testing.T) { for _, p := range All() { s := p.String() if s == "Invalid" { t.Errorf("Perm %d has no name", p) } if s == "" { t.Errorf("Perm %d has empty string", p) } } if Perm(99).String() != "Invalid" { t.Error("out-of-range Perm should be Invalid") } }