Gather data

This commit is contained in:
mike
2026-01-08 02:54:56 +01:00
parent 17880de6d5
commit 5691b4c07b
2 changed files with 64 additions and 13 deletions

View File

@@ -11,6 +11,7 @@ import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.*; import java.util.*;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static puzzle.ExportFormat.*; import static puzzle.ExportFormat.*;
@@ -18,8 +19,10 @@ import static puzzle.SwedishGenerator.*;
import static puzzle.SwedishGenerator.loadWords; import static puzzle.SwedishGenerator.loadWords;
public class Main { public class Main {
// ---------------- Top-level generatePuzzle ---------------- // ---------------- Top-level generatePuzzle ----------------
public record PuzzleResult(SwedishGenerator swe, Dict dict, Grid mask, FillResult filled) { } public record PuzzleResult(SwedishGenerator swe, Dict dict, Grid mask, FillResult filled) { }
final static String OUT_DIR = envOrDefault("OUT_DIR", "/data/puzzle"); final static String OUT_DIR = envOrDefault("OUT_DIR", "/data/puzzle");
final static Path PUZZLE_DIR = Paths.get(OUT_DIR, "puzzles"); final static Path PUZZLE_DIR = Paths.get(OUT_DIR, "puzzles");
static final Path INDEX_FILE = PUZZLE_DIR.resolve("index.json"); static final Path INDEX_FILE = PUZZLE_DIR.resolve("index.json");
@@ -30,12 +33,18 @@ public class Main {
static final Path OUTPUT_PATH = PUZZLE_DIR.resolve(FILE_NAME); static final Path OUTPUT_PATH = PUZZLE_DIR.resolve(FILE_NAME);
static final String DATE_STRING = now.toLocalDate().toString(); static final String DATE_STRING = now.toLocalDate().toString();
static final AtomicLong TOTAL_NODES = new AtomicLong(0);
static final AtomicLong TOTAL_BACKTRACKS = new AtomicLong(0);
static final AtomicLong TOTAL_ATTEMPTS = new AtomicLong(0);
static final AtomicLong TOTAL_SUCCESS = new AtomicLong(0);
static final AtomicLong TOTAL_SIMPLICITY = new AtomicLong(0); // Scaled by 100 for precision
@Data @Data
public static class Opts { public static class Opts {
public int seed = (int) (System.nanoTime() ^ System.currentTimeMillis()); public int seed = (int) (System.nanoTime() ^ System.currentTimeMillis());
public int pop = 18; public int pop = 18;
public int gens = 500; public int gens = 2000;
public String wordsPath = "nl_score_hints.csv"; public String wordsPath = "nl_score_hints.csv";
public double minSimplicity = 0; // 0 means no limit public double minSimplicity = 0; // 0 means no limit
public int threads = Math.max(1, Runtime.getRuntime().availableProcessors()); public int threads = Math.max(1, Runtime.getRuntime().availableProcessors());
@@ -224,6 +233,8 @@ public class Main {
i++; i++;
} else if (a.equals("--reindex")) { } else if (a.equals("--reindex")) {
out.reindex = true; out.reindex = true;
} else if (a.equals("--verbose")) {
out.verbose = true;
} else { } else {
throw new IllegalArgumentException("Unknown arg: " + a); throw new IllegalArgumentException("Unknown arg: " + a);
} }
@@ -245,9 +256,10 @@ public class Main {
info(String.format(Locale.ROOT, "loadTime : %.3f s", (tLoad1 - tLoad0) / 1e9)); info(String.format(Locale.ROOT, "loadTime : %.3f s", (tLoad1 - tLoad0) / 1e9));
section("Search"); section("Search");
var tSearch0 = System.currentTimeMillis();
var deadline = System.currentTimeMillis() + 40_000; var deadline = System.currentTimeMillis() + 40_000;
PuzzleResult resFinal = null;
if (opts.threads > 1) { if (opts.threads > 1) {
info("mode : multi-threaded (" + opts.threads + ")"); info("mode : multi-threaded (" + opts.threads + ")");
var executor = Executors.newFixedThreadPool(opts.threads); var executor = Executors.newFixedThreadPool(opts.threads);
@@ -268,7 +280,8 @@ public class Main {
var result = future.get(); var result = future.get();
if (result != null) { if (result != null) {
info("status : SOLVED"); info("status : SOLVED");
return result; resFinal = result;
break;
} }
// Submit another task if we still have time // Submit another task if we still have time
@@ -277,7 +290,7 @@ public class Main {
completionService.submit(() -> attempt(new Rng(opts.seed + attempt), dict, opts)); completionService.submit(() -> attempt(new Rng(opts.seed + attempt), dict, opts));
} }
} }
warn("status : UNSOLVED (timeout)"); if (resFinal == null) warn("status : UNSOLVED (timeout)");
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
warn("status : INTERRUPTED"); warn("status : INTERRUPTED");
@@ -286,7 +299,6 @@ public class Main {
} finally { } finally {
executor.shutdownNow(); executor.shutdownNow();
} }
return null;
} else { } else {
info("mode : single-threaded"); info("mode : single-threaded");
@@ -301,20 +313,57 @@ public class Main {
if (result != null) { if (result != null) {
info("status : SOLVED"); info("status : SOLVED");
info("foundAtTry : " + attempt); info("foundAtTry : " + attempt);
return result; resFinal = result;
break;
} }
} }
info("status : UNSOLVED (timeout)"); if (resFinal == null) info("status : UNSOLVED (timeout)");
return null;
} }
var tSearch1 = System.currentTimeMillis();
var searchTime = (tSearch1 - tSearch0) / 1000.0;
section("Performance");
info(String.format(Locale.ROOT, "totalNodes : %,d", TOTAL_NODES.get()));
info(String.format(Locale.ROOT, "totalBacks : %,d", TOTAL_BACKTRACKS.get()));
info(String.format(Locale.ROOT, "searchTime : %.2f s", searchTime));
info(String.format(Locale.ROOT, "nodes/sec : %,d", (int) (TOTAL_NODES.get() / Math.max(0.001, searchTime))));
section("Material");
info(String.format(Locale.ROOT, "attempts : %,d", TOTAL_ATTEMPTS.get()));
info(String.format(Locale.ROOT, "successRate : %.1f%%", (TOTAL_ATTEMPTS.get() == 0) ? 0 : (TOTAL_SUCCESS.get() * 100.0 / TOTAL_ATTEMPTS.get())));
if (TOTAL_SUCCESS.get() > 0) {
info(String.format(Locale.ROOT, "avgSimplic : %.2f", (TOTAL_SIMPLICITY.get() / 100.0) / TOTAL_SUCCESS.get()));
}
info(String.format(Locale.ROOT, "dictWords : %,d", dict.wordz().length));
return resFinal;
} }
static PuzzleResult attempt(Rng rng, Dict dict, Opts opts) { static PuzzleResult attempt(Rng rng, Dict dict, Opts opts) {
TOTAL_ATTEMPTS.incrementAndGet();
var swe = new SwedishGenerator(); var swe = new SwedishGenerator();
var mask = swe.generateMask(rng, dict.lenCounts(), opts.pop, opts.gens, opts.verbose); var mask = swe.generateMask(rng, dict.lenCounts(), opts.pop, opts.gens, opts.verbose);
var filled = swe.fillMask(rng, mask, dict.index(), 200, opts.fillTimeout, opts.verbose); var filled = swe.fillMask(rng, mask, dict.index(), 200, opts.fillTimeout, opts.verbose);
TOTAL_NODES.addAndGet(filled.stats().nodes);
TOTAL_BACKTRACKS.addAndGet(filled.stats().backtracks);
if (filled.ok()) {
TOTAL_SUCCESS.incrementAndGet();
TOTAL_SIMPLICITY.addAndGet((long) (filled.simplicity() * 100));
}
var name = Thread.currentThread().getName();
var status = filled.ok() ? "SUCCESS" : "FAILED";
var simplicity = String.format(Locale.ROOT, "%.2f", filled.simplicity());
var nps = (int) (filled.stats().nodes / Math.max(0.001, filled.stats().seconds));
System.out.printf(Locale.ROOT,
"[ATTEMPT] thread=%s | status=%s | nodes=%d | backtracks=%d | nps=%d | simplicity=%s | time=%.1fs%n",
name, status, filled.stats().nodes, filled.stats().backtracks, nps, simplicity, filled.stats().seconds
);
if (filled.ok() && (opts.minSimplicity <= 0 || filled.simplicity() >= opts.minSimplicity)) { if (filled.ok() && (opts.minSimplicity <= 0 || filled.simplicity() >= opts.minSimplicity)) {
return new PuzzleResult(swe, dict, mask, filled); return new PuzzleResult(swe, dict, mask, filled);
} }

View File

@@ -718,7 +718,7 @@ public record SwedishGenerator(int[] buff) {
public FillResult fillMask(Rng rng, Grid mask, DictEntry[] dictIndex, public FillResult fillMask(Rng rng, Grid mask, DictEntry[] dictIndex,
int logEveryMs, int timeLimitMs, boolean verbose) { int logEveryMs, int timeLimitMs, boolean verbose) {
boolean multiThreaded = Thread.currentThread().getName().contains("pool");
var grid = mask.deepCopyGrid(); var grid = mask.deepCopyGrid();
var slots = extractSlots(grid); var slots = extractSlots(grid);
@@ -738,7 +738,7 @@ public record SwedishGenerator(int[] buff) {
final var BAR_LEN = 22; final var BAR_LEN = 22;
Runnable renderProgress = () -> { Runnable renderProgress = () -> {
if (!verbose) return; if (!verbose || multiThreaded) return;
var now = System.currentTimeMillis(); var now = System.currentTimeMillis();
if ((now - lastLog.get()) < logEveryMs) return; if ((now - lastLog.get()) < logEveryMs) return;
lastLog.set(now); lastLog.set(now);
@@ -901,14 +901,16 @@ public record SwedishGenerator(int[] buff) {
renderProgress.run(); renderProgress.run();
var ok = new Solver().backtrack(0); var ok = new Solver().backtrack(0);
// final progress line // final progress line
if (!multiThreaded) {
System.out.print("\r" + padRight("", 120) + "\r"); System.out.print("\r" + padRight("", 120) + "\r");
System.out.flush(); System.out.flush();
}
stats.seconds = (System.currentTimeMillis() - t0) / 1000.0; stats.seconds = (System.currentTimeMillis() - t0) / 1000.0;
var res = new FillResult(ok, grid, assigned, stats); var res = new FillResult(ok, grid, assigned, stats);
// print a final progress line // print a final progress line
if (verbose) { if (verbose && !multiThreaded) {
System.out.println( System.out.println(
String.format(Locale.ROOT, String.format(Locale.ROOT,
"[######################] %d/%d slots | nodes=%d | backtracks=%d | mrv=%d | %.1fs", "[######################] %d/%d slots | nodes=%d | backtracks=%d | mrv=%d | %.1fs",