Gather data
This commit is contained in:
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user