# coverage_check_canonical.py # Author: David Niedbala Giraudin, ORCID 0009-0009-1526-1178 # Technical assistance: Claude (Anthropic) # # Coverage check of the cubes, first method: canonical forms. # A pattern of G_1 is (M, miss): M is a matching on the positions 0..4 of G_1 (its inner edges); # miss maps some matched positions to an empty cross slot in {3,...,8}; unmatched positions have # their own slot empty. The acting group is S_5 (positions) x W, where W is the group of the 48 # permutations of {3,...,8} preserving the pairs {3,4},{5,6},{7,8}. For every pattern the # canonical form is the least image under the whole group (5! * 48 = 5760 elements). # Checked: the 39 cubes of cubesA.json (Program A) and the 39 representatives of reps39.json # (Program B) each contain exactly one pattern of every orbit. # Pure Python, no arguments, about one minute. import itertools, json, os os.chdir(os.path.dirname(os.path.abspath(__file__))) POS = range(5) PERMS = list(itertools.permutations(POS)) pairs = [(3, 4), (5, 6), (7, 8)] W = [] for perm in itertools.permutations(range(3)): for flips in itertools.product((0, 1), repeat=3): w = {} for t in range(3): a, b = pairs[t]; c, d = pairs[perm[t]] if flips[t]: c, d = d, c w[a] = c; w[b] = d W.append(w) assert len(W) == 48 def canon(M, miss): best = None for pi in PERMS: Mp = tuple(sorted(tuple(sorted((pi[a], pi[b]))) for a, b in M)) for w in W: k = (Mp, tuple(sorted((pi[p], w[c]) for p, c in miss))) if best is None or k < best: best = k return best def matchings(): edges = list(itertools.combinations(POS, 2)) yield () for e in edges: yield (e,) for e, f in itertools.combinations(edges, 2): if not set(e) & set(f): yield (e, f) orbits = set(); npat = 0 # every matching is S_5-equivalent to (), ((0,1),) or ((0,1),(2,3)); enumerating these three with all # choices of empty slots reaches every orbit for M in [(), ((0, 1),), ((0, 1), (2, 3))]: matched = sorted(p for e in M for p in e) for ch in itertools.product([None, 3, 4, 5, 6, 7, 8], repeat=len(matched)): miss = tuple((p, c) for p, c in zip(matched, ch) if c is not None) orbits.add(canon(M, miss)) total = sum(1 for M in matchings() for _ in itertools.product(range(7), repeat=2 * len(M))) print('patterns: %d, orbits: %d' % (total, len(orbits)), flush=True) def check(fname, conv_miss): data = json.load(open(fname)) keys = [canon(tuple(tuple(e) for e in c['own']), tuple((int(p), conv_miss(w)) for p, w in c['miss'].items())) for c in data] hit = set(keys) dup = len(keys) - len(hit) print('%s: %d entries, %d orbits hit, %d hit twice, %d orbits missed, %d keys outside the orbit list' % (fname, len(keys), len(hit & orbits), dup, len(orbits - hit), len(hit - orbits)), flush=True) return len(hit) == len(orbits) == len(keys) and hit == orbits ok1 = check('cubesA.json', lambda w: w) # groups numbered 1..8 (G_3..G_8 = 3..8) ok2 = check('reps39.json', lambda w: w) # same numbering 3..8 for the empty cross slots print('RESULT:', 'both cube lists cover every orbit exactly once' if ok1 and ok2 else 'FAILED', flush=True)