fragility.py
5.5 kB · python · 163 lines
1import collections2import itertools3import sys4import time5from fractions import Fraction67import numpy as np89SPAN = 1310SIZE = 611ORDERS = (2, 3)12DIAL = ([0, 1, 2], [0, 1, 4, 6], [0, 1, 2, 3, 7], [0, 1, 4, 7, 9], [0, 1, 2, 3, 7, 11])13CAP = 1700014LAGS = 8_000_0001516def coarray(S):17 return {x - y for x in S for y in S}1819def weights(S):20 w = collections.Counter()21 for x in S:22 for y in S:23 w[x - y] += 124 return w2526def design(G, b, r):27 F = [0]28 for i in range(r):29 F = sorted({f + g * b ** i for f in F for g in G})30 return F3132def digits(s, b, r):33 return [(s // b ** i) % b for i in range(r)]3435def brute(S):36 D = coarray(S)37 return {s for s in S if coarray([x for x in S if x != s]) != D}3839def fast(S):40 pairs = collections.defaultdict(list)41 for x in S:42 for y in S:43 if x != y and len(pairs[x - y]) < 3:44 pairs[x - y].append((x, y))45 E = set()46 for p in pairs.values():47 if len(p) == 1:48 E.update(p[0])49 elif len(p) == 2:50 E.update(set(p[0]) & set(p[1]))51 return E5253def paired(G):54 w = weights(G)55 return {g for g in G if any(w[g - h] == 1 for h in G if h != g)}5657def holefree(G):58 a = max(G) - min(G)59 return coarray(G) == set(range(-a, a + 1))6061def generators():62 for a in range(1, SPAN + 1):63 for L in range(2, SIZE + 1):64 for mid in itertools.combinations(range(1, a), L - 2):65 G = [0, *mid, a]66 if tuple(a - x for x in reversed(G)) < tuple(G):67 continue68 if holefree(G):69 yield G7071def exact():72 t0 = time.time()73 gens = list(generators())74 cases = bad = tight_bad = 075 loose = []76 for G in gens:77 L, a = len(G), max(G)78 M = 2 * a + 179 U = paired(G)80 E = brute(G)81 assert U <= E82 for r in ORDERS:83 F = design(G, M, r)84 assert len(F) == L ** r and coarray(F) == set(range(-(M ** r - 1) // 2, (M ** r + 1) // 2))85 got = brute(F)86 want = {s for s in F if all(d in U for d in digits(s, M, r))}87 cases += 188 bad += got != want89 tight_bad += (len(got) == len(E) ** r) != (E == U)90 assert fast(F) == got91 if E != U:92 loose.append((G, L, len(E), len(U)))93 econ = [x for x in loose if x[2] == x[1]]94 print(f"generators {len(gens)} cases {cases} mismatches {bad} tightness-criterion failures {tight_bad}")95 print(f"loose generators (F_G > u/L) {len(loose)}, of them maximally economic (F_G = 1) {len(econ)}")96 byL = collections.Counter(x[1] for x in econ)97 print(f" maximally economic loose by L {dict(sorted(byL.items()))}; card E(G) - u over the loose {sorted({e - u for _, _, e, u in loose})}")98 for G, L, e, u in loose:99 print(f" {G} L {L} essential {e} paired {u}{' economic' if e == L else ''}")100 for name, G in (("ula3", [0, 1, 2]), ("mra4", [0, 1, 4, 6]), ("ula4", [0, 1, 2, 3])):101 M = 2 * max(G) + 1102 row = [(len(brute(design(G, M, r))), len(G) ** r) for r in (1, 2, 3)]103 print(f" {name} {G} M {M} (essential, sensors) at r = 1, 2, 3: {row}")104 print(f"exact {time.time() - t0:.1f}s")105106def law(row, u):107 if all(e == u ** (i + 1) for i, e in enumerate(row) if i):108 return "u^r"109 for k in (2, 3):110 tail = row[k - 1:]111 if len(tail) < 3:112 continue113 d0, d1 = tail[1] - tail[0], tail[2] - tail[1]114 lam = Fraction(d1, d0) if d0 else Fraction(1)115 c = tail[1] - lam * tail[0]116 if lam >= 1 and all(tail[j + 1] == lam * tail[j] + c for j in range(len(tail) - 1)):117 return f"e_(r+1) = {lam} e_r + {c} from r = {k}, fitted on 2 steps, checked on {len(tail) - 3}"118 return "no affine law"119120def ess(F, w, A):121 F = np.asarray(F, dtype=np.int64)122 inside = np.zeros(A + 1, dtype=bool)123 inside[F] = True124 out = np.zeros(len(F), dtype=bool)125 ch = max(1, 4_000_000 // len(F))126 for i in range(0, len(F), ch):127 s = F[i:i + ch, None]128 t = s - F[None, :]129 W = w[t + A]130 k = s + t131 two = (W == 2) & (t != 0) & (k >= 0) & (k <= A) & inside[np.clip(k, 0, A)]132 out[i:i + ch] = ((W == 1) & (t != 0)).any(axis=1) | two.any(axis=1)133 return set(F[out].tolist())134135def dial():136 t0 = time.time()137 for G in DIAL:138 a, L, u = max(G), len(G), len(paired(G))139 wG = weights(G)140 for b in range(a + 1, 2 * a + 2):141 row, lags, w, A = [], 0, np.ones(1, dtype=np.int64), 0142 for r in range(1, 12):143 An = a * (b ** r - 1) // (b - 1)144 if L ** r > CAP or 2 * An + 1 > LAGS:145 break146 wn = np.zeros(2 * An + 1, dtype=np.int64)147 for d, m in wG.items():148 lo = An + d * b ** (r - 1) - A149 wn[lo:lo + 2 * A + 1] += m * w150 w, A = wn, An151 F = design(G, b, r)152 assert len(F) == L ** r and F[-1] == A and (w > 0).all() and w.sum() == len(F) ** 2153 E = ess(F, w, A)154 assert len(F) > 250 or (E == brute(F) == fast(F) and weights(F) == {t - A: int(x) for t, x in enumerate(w) if x})155 row.append(len(E))156 lags = 2 * A + 1157 print(f" {G} b {b} e_r {row} {law(row, u)}; {L ** len(row)} sensors, {lags} lags at the last r")158 print(f"dial {time.time() - t0:.1f}s")159160if __name__ == "__main__":161 verbs = sys.argv[1:] or ["exact", "dial"]162 for v in verbs:163 {"exact": exact, "dial": dial}[v]()