Gather data

This commit is contained in:
mike
2026-01-05 23:43:44 +01:00
parent f031591105
commit 5bba12caf8
3 changed files with 77 additions and 41 deletions

View File

@@ -12,6 +12,11 @@
</properties> </properties>
<dependencies> <dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.13.2</version>
</dependency>
<dependency> <dependency>
<groupId>org.postgresql</groupId> <groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId> <artifactId>postgresql</artifactId>

View File

@@ -137,7 +137,6 @@ public class Main {
System.out.printf(Locale.ROOT, " %-14s: %s%n", "wordsPath", o.wordsPath); System.out.printf(Locale.ROOT, " %-14s: %s%n", "wordsPath", o.wordsPath);
System.out.printf(Locale.ROOT, " %-14s: %.2f%n", "minSimplicity", o.minSimplicity); System.out.printf(Locale.ROOT, " %-14s: %.2f%n", "minSimplicity", o.minSimplicity);
System.out.printf(Locale.ROOT, " %-14s: %d%n", "threads", o.threads); System.out.printf(Locale.ROOT, " %-14s: %d%n", "threads", o.threads);
System.out.printf(Locale.ROOT, " %-14s: %d%n", "maxTries", o.tries);
} }
private static String fmtPoint(int r, int c) { return String.format(Locale.ROOT, "(%d,%d)", r, c); } private static String fmtPoint(int r, int c) { return String.format(Locale.ROOT, "(%d,%d)", r, c); }
@@ -181,8 +180,7 @@ public class Main {
Defaults: Defaults:
--pop 18 --pop 18
--gens 600 --gens 500
--tries = threads
--words nl_score_hints.csv --words nl_score_hints.csv
--min-simplicity 0 (no limit) --min-simplicity 0 (no limit)
--threads %d --threads %d
@@ -245,33 +243,62 @@ public class Main {
section("Search"); section("Search");
var deadline = System.currentTimeMillis() + 40_000;
var fillTimeout = 20_000;
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);
var completionService = new ExecutorCompletionService<PuzzleResult>(executor);
int submitted = 0;
try { try {
var tasks = new ArrayList<Callable<PuzzleResult>>(); // Keep at least some tasks in flight
for (var i = 1; i <= opts.tries; i++) { for (int i = 0; i < opts.threads; i++) {
final var attempt = i; final int attempt = ++submitted;
tasks.add(() -> { completionService.submit(() -> {
var threadRng = new Rng(opts.seed + attempt); var threadRng = new Rng(opts.seed + attempt);
var mask = generateMask(threadRng, dict.lenCounts(), opts.pop, opts.gens, false); var mask = generateMask(threadRng, dict.lenCounts(), opts.pop, opts.gens, false);
var filled = fillMask(threadRng, mask, dict.index(), dict.words(), 200, 30000, false); var filled = fillMask(threadRng, mask, dict.index(), dict.words(), 200, fillTimeout, false);
if (filled.ok && (opts.minSimplicity <= 0 || filled.simplicity >= opts.minSimplicity)) { if (filled.ok && (opts.minSimplicity <= 0 || filled.simplicity >= opts.minSimplicity)) {
info("status : SOLVED");
info("foundAtTry : " + attempt);
return new PuzzleResult(dict, mask, filled); return new PuzzleResult(dict, mask, filled);
} }
throw new RuntimeException("No solution in try " + attempt); return null;
}); });
} }
return executor.invokeAny(tasks);
while (System.currentTimeMillis() < deadline) {
var future = completionService.poll(deadline - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
if (future == null) break;
var result = future.get();
if (result != null) {
info("status : SOLVED");
return result;
}
// Submit another task if we still have time
if (System.currentTimeMillis() < deadline) {
final int attempt = ++submitted;
completionService.submit(() -> {
var threadRng = new Rng(opts.seed + attempt);
var mask = generateMask(threadRng, dict.lenCounts(), opts.pop, opts.gens, false);
var filled = fillMask(threadRng, mask, dict.index(), dict.words(), 200, fillTimeout, false);
if (filled.ok && (opts.minSimplicity <= 0 || filled.simplicity >= opts.minSimplicity)) {
return new PuzzleResult(dict, mask, filled);
}
return null;
});
}
}
warn("status : UNSOLVED (timeout)");
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
warn("status : INTERRUPTED"); warn("status : INTERRUPTED");
} catch (ExecutionException e) { } catch (ExecutionException e) {
// all failed warn("status : ERROR (" + e.getMessage() + ")");
warn("status : UNSOLVED");
} finally { } finally {
executor.shutdownNow(); executor.shutdownNow();
} }
@@ -279,13 +306,15 @@ public class Main {
} else { } else {
info("mode : single-threaded"); info("mode : single-threaded");
var rng = new Rng(opts.seed); var rng = new Rng(opts.seed);
int attempt = 0;
for (var attempt = 1; attempt <= opts.tries; attempt++) { while (System.currentTimeMillis() < deadline) {
info("try : " + attempt + "/" + opts.tries); attempt++;
info("try : " + attempt + " (remaining: " + (deadline - System.currentTimeMillis()) / 1000 + "s)");
var mask = generateMask(rng, dict.lenCounts(), opts.pop, opts.gens, true); var mask = generateMask(rng, dict.lenCounts(), opts.pop, opts.gens, true);
var filled = fillMask(rng, mask, dict.index(), dict.words(), 200, 30000, true); var filled = fillMask(rng, mask, dict.index(), dict.words(), 200, fillTimeout, true);
if (filled.ok && (opts.minSimplicity <= 0 || filled.simplicity >= opts.minSimplicity)) { if (filled.ok && (opts.minSimplicity <= 0 || filled.simplicity >= opts.minSimplicity)) {
info("status : SOLVED"); info("status : SOLVED");
@@ -301,7 +330,7 @@ public class Main {
} }
} }
info("status : UNSOLVED"); info("status : UNSOLVED (timeout)");
return null; return null;
} }
} }
@@ -397,7 +426,7 @@ public class Main {
info("Rebuilding index from: " + PUZZLE_DIR); info("Rebuilding index from: " + PUZZLE_DIR);
List<String> records = new ArrayList<>(); var records = new ArrayList<String>();
try (var stream = Files.list(PUZZLE_DIR)) { try (var stream = Files.list(PUZZLE_DIR)) {
stream.filter(p -> p.toString().endsWith(".json") && !p.getFileName().toString().equals("index.json")) stream.filter(p -> p.toString().endsWith(".json") && !p.getFileName().toString().equals("index.json"))
.sorted(Comparator.comparing(Path::getFileName).reversed()) .sorted(Comparator.comparing(Path::getFileName).reversed())

View File

@@ -5,7 +5,7 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.*; import java.util.*;
import java.util.concurrent.*; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@@ -132,7 +132,7 @@ public class SwedishGenerator {
int[] data() { return a; } // note: may have extra capacity int[] data() { return a; } // note: may have extra capacity
} }
static record DictEntry(ArrayList<String> words, IntList[][] pos) { static record DictEntry(ArrayList<Lemma> words, IntList[][] pos) {
public DictEntry(int L) { public DictEntry(int L) {
this(new ArrayList<>(), new IntList[L][26]); this(new ArrayList<>(), new IntList[L][26]);
@@ -142,14 +142,14 @@ public class SwedishGenerator {
} }
} }
static record WordDifficulty(String word, int difficulty, int simpel, int score, int cross, ArrayList<String> clue) { static record Lemma(String word, int difficulty, int simpel, int score, int cross, ArrayList<String> clue) {
public WordDifficulty(String word, int simpel, int score, String clue) { public Lemma(String word, int simpel, int score, String clue) {
var difficulty1 = 0 + ((8 - word.length()) * 30) + ((10 - score) * 15); var complex = 0 + ((8 - word.length()) * 30) + ((10 - score) * 15);
var crossScore = ThemePoolBuilderLength.crossabilityScore(word); var crossScore = ThemePoolBuilderLength.crossabilityScore(word);
var list = new ArrayList<String>(10); var list = new ArrayList<String>(10);
list.add(clue); list.add(clue);
this(word, difficulty1, simpel, score, (crossScore * 7) + ((score) * 30) + ((word.length()) * 15), list); this(word, complex, simpel, score, (crossScore * 7) + ((score) * 30) + ((word.length()) * 15), list);
// Prioritize simple words (high lScore) and long words. // Prioritize simple words (high lScore) and long words.
// lScore (1-10) adds up to 1000 points (weight 100). // lScore (1-10) adds up to 1000 points (weight 100).
@@ -161,9 +161,10 @@ public class SwedishGenerator {
// Length impact: up to 8 * 10 = 80 // Length impact: up to 8 * 10 = 80
// Score impact: up to 10 * 15 = 150 // Score impact: up to 10 * 15 = 150
} }
char charAt(int idx) { return word.charAt(idx); }
} }
public static record Dict(Map<String, WordDifficulty> words, public static record Dict(Map<String, Lemma> words,
HashMap<Integer, DictEntry> index, HashMap<Integer, DictEntry> index,
HashMap<Integer, Integer> lenCounts) { } HashMap<Integer, Integer> lenCounts) { }
static Dict loadWords(String wordsPath) { static Dict loadWords(String wordsPath) {
@@ -171,10 +172,11 @@ public class SwedishGenerator {
try { try {
raw = Files.readString(Path.of(wordsPath), StandardCharsets.UTF_8); raw = Files.readString(Path.of(wordsPath), StandardCharsets.UTF_8);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace();
raw = "WOORD,level_1_to_10,hint\nEU,2,hint\nUUR,2,hint\nAUTO,2,hint\nBOOM,2,hint\nHUIS,2,hint\nKAT,2,hint\nZEE,2,hint\nRODE,2,hint\nDRAAD,2,hint\nKENNIS,2,hint\nNETWERK,2,hint\nPAKTE,2,hint\n"; raw = "WOORD,level_1_to_10,hint\nEU,2,hint\nUUR,2,hint\nAUTO,2,hint\nBOOM,2,hint\nHUIS,2,hint\nKAT,2,hint\nZEE,2,hint\nRODE,2,hint\nDRAAD,2,hint\nKENNIS,2,hint\nNETWERK,2,hint\nPAKTE,2,hint\n";
} }
var map = new HashMap<String, WordDifficulty>(); var map = new HashMap<String, Lemma>();
boolean first = true; boolean first = true;
for (var line : raw.split("\\R")) { for (var line : raw.split("\\R")) {
if (line.isBlank()) continue; if (line.isBlank()) continue;
@@ -200,7 +202,7 @@ public class SwedishGenerator {
if (map.containsKey(s)) { if (map.containsKey(s)) {
map.get(s).clue.add(rawClue); map.get(s).clue.add(rawClue);
} else { } else {
map.put(s, new WordDifficulty(s, simpel, score, rawClue)); map.put(s, new Lemma(s, simpel, score, rawClue));
} }
} }
} }
@@ -223,7 +225,7 @@ public class SwedishGenerator {
} }
var idx = entry.words.size(); var idx = entry.words.size();
entry.words.add(w.word); entry.words.add(w);
for (var i = 0; i < L; i++) { for (var i = 0; i < L; i++) {
var letter = w.word.charAt(i) - 'A'; var letter = w.word.charAt(i) - 'A';
@@ -618,7 +620,7 @@ public class SwedishGenerator {
return cross * 10 + s.len; return cross * 10 + s.len;
} }
static Undo placeWord(char[][] grid, Slot s, String w) { static Undo placeWord(char[][] grid, Slot s, Lemma w) {
var urs = new int[s.len]; var urs = new int[s.len];
var ucs = new int[s.len]; var ucs = new int[s.len];
var up = new char[s.len]; var up = new char[s.len];
@@ -648,7 +650,7 @@ public class SwedishGenerator {
} }
static FillResult fillMask(Rng rng, char[][] mask, HashMap<Integer, DictEntry> dictIndex, static FillResult fillMask(Rng rng, char[][] mask, HashMap<Integer, DictEntry> dictIndex,
Map<String, WordDifficulty> llmScores, Map<String, Lemma> llmScores,
int logEveryMs, int timeLimitMs, boolean verbose) { int logEveryMs, int timeLimitMs, boolean verbose) {
var grid = deepCopyGrid(mask); var grid = deepCopyGrid(mask);
@@ -757,9 +759,9 @@ public class SwedishGenerator {
var entry = dictIndex.get(s.len); var entry = dictIndex.get(s.len);
var pat = patternForSlot(grid, s); var pat = patternForSlot(grid, s);
java.util.function.Function<String, Boolean> tryWord = (String w) -> { Predicate<Lemma> tryWord = (Lemma w) -> {
if (w == null) return false; if (w == null) return false;
if (used.contains(w)) return false; if (used.contains(w.word())) return false;
for (var i = 0; i < pat.length; i++) { for (var i = 0; i < pat.length; i++) {
if (pat[i] != 0 && pat[i] != w.charAt(i)) return false; if (pat[i] != 0 && pat[i] != w.charAt(i)) return false;
@@ -768,8 +770,8 @@ public class SwedishGenerator {
var undo = placeWord(grid, s, w); var undo = placeWord(grid, s, w);
if (undo == null) return false; if (undo == null) return false;
used.add(w); used.add(w.word());
assigned.put(k, w); assigned.put(k, w.word());
if (backtrack()) return true; if (backtrack()) return true;
@@ -792,7 +794,7 @@ public class SwedishGenerator {
int idxInArray = (int) (r * r * r * L); int idxInArray = (int) (r * r * r * L);
var idx = idxs[idxInArray]; var idx = idxs[idxInArray];
var w = entry.words.get(idx); var w = entry.words.get(idx);
if (tryWord.apply(w)) return true; if (tryWord.test(w)) return true;
} }
stats.backtracks++; stats.backtracks++;
return false; return false;
@@ -809,7 +811,7 @@ public class SwedishGenerator {
double r = rng.nextFloat(); double r = rng.nextFloat();
int idxInArray = (int) (r * r * r * N); int idxInArray = (int) (r * r * r * N);
var w = entry.words.get(idxInArray); var w = entry.words.get(idxInArray);
if (tryWord.apply(w)) return true; if (tryWord.test(w)) return true;
} }
stats.backtracks++; stats.backtracks++;