| """Worked example of superposition-instance generation for one puzzle/stage. |
| |
| Prints the candidate sets, the dependencies read off the mask, the instances that |
| survived, the ones that were ruled out, and the resulting per-cell coverage. |
| Display is restricted to one unit so the table is readable; generation always |
| runs on the full grid. |
| """ |
| import argparse |
|
|
| import numpy as np |
|
|
| import superposition_instances as SI |
|
|
| NAMES = ([f"row {r}" for r in range(9)] + [f"col {c}" for c in range(9)] |
| + [f"box {b}" for b in range(9)]) |
|
|
|
|
| def fmt(cells): |
| return "{" + ",".join(str(d) for d in cells) + "}" |
|
|
|
|
| def main(): |
| ap = argparse.ArgumentParser() |
| ap.add_argument("--masks", default="datasets_multicandidate_s12/test_cand_masks.npy") |
| ap.add_argument("--puzzle", type=int, default=2) |
| ap.add_argument("--stage", type=int, default=0) |
| ap.add_argument("--unit", type=int, default=14, help="unit index to display (14 = col 5)") |
| ap.add_argument("--max-confine", type=int, default=3) |
| ap.add_argument("--max-instances", type=int, default=16) |
| args = ap.parse_args() |
|
|
| masks = np.load(args.masks, mmap_mode="r") |
| mask = np.array(masks[args.puzzle, args.stage]).astype(np.uint16) |
|
|
| unit = SI.UNITS[args.unit] |
| forced, empties = SI.split_cells(mask) |
|
|
| print("=" * 78) |
| print(f"puzzle {args.puzzle}, stage {args.stage}, displaying {NAMES[args.unit]}") |
| print("=" * 78) |
| print(f"grid: {len(forced)} determined cells, {len(empties)} undetermined, " |
| f"mean candidate count {np.mean([len(SI.digits_of(mask[c])) for c in empties]):.2f}") |
|
|
| print(f"\ncandidate sets in {NAMES[args.unit]}:") |
| for c in unit: |
| r, cc = divmod(c, 9) |
| ds = SI.digits_of(mask[c]) |
| tag = "determined" if len(ds) == 1 else "" |
| print(f" r{r}c{cc} {fmt(ds):<22} {tag}") |
|
|
| all_deps = SI.dependencies_from_mask(mask, max_confine=9) |
| conf_deps = SI.dependencies_from_mask(mask, max_confine=args.max_confine) |
| print(f"\ndependencies over the whole grid:") |
| print(f" {len(all_deps)} total (every unit, every unplaced digit)") |
| print(f" {len(conf_deps)} with confinement size <= {args.max_confine} " |
| f"(these are the ones enforced)") |
| sizes = {} |
| for _, S in all_deps: |
| sizes[len(S)] = sizes.get(len(S), 0) + 1 |
| print(" by confinement size: " |
| + ", ".join(f"|S|={k}: {v}" for k, v in sorted(sizes.items()))) |
|
|
| print(f"\ndependencies enforced inside {NAMES[args.unit]}:") |
| shown = 0 |
| for d, S in conf_deps: |
| if not set(S) <= set(unit): |
| continue |
| locs = " or ".join(f"r{c//9}c{c%9}" for c in S) |
| print(f" digit {d} must be placed at {locs}") |
| shown += 1 |
| if shown == 0: |
| print(" (none at this confinement threshold)") |
|
|
| res = SI.instances_for_stage(mask, max_confine=args.max_confine, |
| max_instances=args.max_instances, seed=0) |
|
|
| print(f"\ngeneration: {res['n_instances']} instances kept, " |
| f"{res['n_rejected']} ruled out over {res['n_attempts']} attempts") |
| print(f"coverage: {res['n_covered']}/{res['n_pairs']} " |
| f"(cell, candidate) pairs = {100*res['coverage']:.1f}%") |
|
|
| inst = res["instances"] |
| if len(inst): |
| print(f"\nwhat each instance assigned in {NAMES[args.unit]}:") |
| hdr = " inst " + " ".join(f"r{c//9}c{c%9}" for c in unit) |
| print(hdr) |
| for i, a in enumerate(inst): |
| print(f" {i:>4} " + " ".join(f"{a[c]:>4}" for c in unit)) |
|
|
| print(f"\nper-cell coverage in {NAMES[args.unit]}:") |
| for c in unit: |
| ds = set(SI.digits_of(mask[c])) |
| seen = set(int(a[c]) for a in inst) |
| miss = sorted(ds - seen) |
| r, cc = divmod(c, 9) |
| status = "complete" if not miss else f"missing {fmt(miss)}" |
| print(f" r{r}c{cc} candidates {fmt(sorted(ds)):<22} " |
| f"seen {fmt(sorted(seen)):<22} {status}") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|