counting.py
3.1 kB · python · 113 lines
1import sys2from fractions import Fraction3from math import factorial, gcd4from collections import Counter56sys.set_int_max_str_digits(1000000)78# ANALYTIC COUNTING - THE UNIVERSE WITHOUT ENUMERATION910def _mobius(n):11 x = n12 mu = 113 p = 214 while p * p <= x:15 if x % p == 0:16 x //= p17 if x % p == 0:18 return 019 mu = -mu20 p += 121 if x > 1:22 mu = -mu23 return mu2425def _divisors(n):26 return [d for d in range(1, n + 1) if n % d == 0]2728def _pos_block_cycles(length):29 out = Counter()30 for period in _divisors(length):31 strings = sum(_mobius(period // d) * 2 ** d for d in _divisors(period))32 out[period] += strings // period33 return out3435def _neg_block_cycles(length):36 def step(x):37 bits = [(x >> i) & 1 for i in range(length)]38 new = [bits[(i - 1) % length] for i in range(length)]39 new[0] ^= 140 return sum(new[i] << i for i in range(length))41 seen = [False] * (2 ** length)42 out = Counter()43 for start in range(2 ** length):44 if seen[start]:45 continue46 run = 047 j = start48 while not seen[j]:49 seen[j] = True50 j = step(j)51 run += 152 out[run] += 153 return out5455def _combine(c1, c2):56 out = Counter()57 for l1, n1 in c1.items():58 for l2, n2 in c2.items():59 g = gcd(l1, l2)60 out[l1 * l2 // g] += n1 * n2 * g61 return out6263def _class_cycles(pos, neg):64 blocks = [_pos_block_cycles(L) for L in pos] + [_neg_block_cycles(L) for L in neg]65 if not blocks:66 return 167 acc = blocks[0]68 for b in blocks[1:]:69 acc = _combine(acc, b)70 return sum(acc.values())7172def _partitions(n, m=None):73 if m is None:74 m = n75 if n == 0:76 yield ()77 return78 for k in range(min(n, m), 0, -1):79 for rest in _partitions(n - k, k):80 yield (k,) + rest8182def _bipartitions(dimension):83 for s in range(dimension + 1):84 for pos in (_partitions(s) if s > 0 else [()]):85 for neg in (_partitions(dimension - s) if (dimension - s) > 0 else [()]):86 yield pos, neg8788def _class_size(pos, neg, dimension):89 centralizer = 190 for part in (pos, neg):91 for length, mult in Counter(part).items():92 centralizer *= factorial(mult) * ((2 * length) ** mult)93 return (2 ** dimension * factorial(dimension)) // centralizer9495def total_designs(dimension):96 return 2 ** (2 ** dimension)9798def distinct_designs(dimension):99 total = Fraction(0)100 order = 2 ** dimension * factorial(dimension)101 checked = 0102 for pos, neg in _bipartitions(dimension):103 size = _class_size(pos, neg, dimension)104 checked += size105 total += size * (2 ** _class_cycles(pos, neg))106 if checked != order:107 raise ValueError("class sizes do not sum to the group order.")108 if total.denominator != 1:109 raise ValueError("Burnside average is not an integer.")110 return int(total // order)111112def sequence(max_dimension):113 return [distinct_designs(d) for d in range(1, max_dimension + 1)]