From 75f599318aee63b7540b7b8e0a622e718b70a00a Mon Sep 17 00:00:00 2001 From: mike Date: Wed, 14 Jan 2026 18:42:13 +0100 Subject: [PATCH] introduce bitloops --- src/main/java/puzzle/Main.java | 22 ++++++++++---- src/main/java/puzzle/SwedishGenerator.java | 29 +++++++++---------- src/test/java/puzzle/MainTest.java | 20 +++++++------ .../java/puzzle/SwedishGeneratorTest.java | 12 ++++---- 4 files changed, 48 insertions(+), 35 deletions(-) diff --git a/src/main/java/puzzle/Main.java b/src/main/java/puzzle/Main.java index 081c12b..6fd7130 100644 --- a/src/main/java/puzzle/Main.java +++ b/src/main/java/puzzle/Main.java @@ -44,7 +44,9 @@ public class Main { public static class Opts { public int seed = (int) (System.nanoTime() ^ System.currentTimeMillis()); - public int pop = 24; + public int clueSize = 24; + public int pop = 40; + public int offspring = 60; public int gens = 700; public String wordsPath = "nl_score_hints_v3.csv"; public double minSimplicity = 0; // 0 means no limit @@ -153,7 +155,9 @@ public class Main { private static void printSettings(Opts o) { System.out.printf(Locale.ROOT, " %-14s: %d%n", "seed", o.seed); + System.out.printf(Locale.ROOT, " %-14s: %d%n", "clues", o.clueSize); System.out.printf(Locale.ROOT, " %-14s: %d%n", "population", o.pop); + System.out.printf(Locale.ROOT, " %-14s: %d%n", "offspring", o.offspring); System.out.printf(Locale.ROOT, " %-14s: %d%n", "generations", o.gens); System.out.printf(Locale.ROOT, " %-14s: %s%n", "wordsPath", o.wordsPath); System.out.printf(Locale.ROOT, " %-14s: %.2f%n", "minSimplicity", o.minSimplicity); @@ -196,11 +200,13 @@ public class Main { static void usage() { System.out.println(""" Usage: - java puzzle.Main [--seed N] [--pop N] [--gens N] [--tries N] [--words FILE] [--min-simplicity N.N] [--threads N] [--reindex] + java puzzle.Main [--seed N] [--clues N] [--pop N] [--offspring N] [--gens N] [--tries N] [--words FILE] [--min-simplicity N.N] [--threads N] [--reindex] Defaults: - --pop 18 - --gens 500 + --clues 18 + --pop 40 + --offspring 60 + --gens 700 --words nl_score_hints.csv --min-simplicity 0 (no limit) --threads %d @@ -221,9 +227,15 @@ public class Main { if (a.equals("--seed")) { out.seed = Integer.parseInt(v); i++; + } else if (a.equals("--clues")) { + out.clueSize = Integer.parseInt(v); + i++; } else if (a.equals("--pop")) { out.pop = Integer.parseInt(v); i++; + } else if (a.equals("--offspring")) { + out.offspring = Integer.parseInt(v); + i++; } else if (a.equals("--gens")) { out.gens = Integer.parseInt(v); i++; @@ -359,7 +371,7 @@ public class Main { static PuzzleResult _attempt(Rng rng, Dict dict, Opts opts) { TOTAL_ATTEMPTS.incrementAndGet(); var swe = new SwedishGenerator(rng, new int[STACK_SIZE], Clues.createEmpty()); - var mask = swe.generateMask(opts.pop, opts.gens, Math.max(opts.pop, (int) Math.floor(opts.pop * 1.5))); + var mask = swe.generateMask(opts.clueSize, opts.pop, opts.gens, opts.offspring); var filled = fillMask(rng, extractSlots(mask, dict.index()), mask.toGrid()); diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index 49f86a8..32b01be 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -59,7 +59,6 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { static final double SIZED = (double) SIZE;// ~18 static final long MASK_LO = (SIZE >= 64) ? -1L : (1L << SIZE) - 1; static final long MASK_HI = (SIZE <= 64) ? 0L : (SIZE >= 128 ? -1L : (1L << (SIZE - 64)) - 1); - static final int TARGET_CLUES = 24; static final int MAX_WORD_LENGTH = C <= R ? C : R; static final int MAX_WORD_LENGTH_PLUS_ONE = MAX_WORD_LENGTH + 1; static final int MIN_LEN = Config.MIN_LEN; @@ -433,11 +432,11 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { } /// does not modify the grid - long maskFitness(final Clues grid) { + long maskFitness(final Clues grid, int clueSize) { long cHLo = 0L, cHHi = 0L, cVLo = 0L, cVHi = 0L; long lo_cl = grid.lo, hi_cl = grid.hi; - long penalty = (((long) Math.abs(grid.clueCount() - TARGET_CLUES)) * 16000L); + long penalty = (((long) Math.abs(grid.clueCount() - clueSize)) * 16000L); boolean hasSlots = false; for (long bits = lo_cl; bits != X; bits &= bits - 1) { @@ -600,9 +599,9 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { return penalty; } - Clues randomMask() { + Clues randomMask(final int clueSize) { var g = Clues.createEmpty(); - for (int placed = 0, guard = 0, idx; placed < TARGET_CLUES && guard < 4000; guard++) { + for (int placed = 0, guard = 0, idx; placed < clueSize && guard < 4000; guard++) { idx = rng.randint(0, SIZE_MIN_1); if (g.isClue(idx)) continue; var d_idx = rng.randint2bitByte(); @@ -653,15 +652,15 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { } public static void clearClues(Clues out, int idx) { if (!out.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(idx, out.digitAt(idx))])) out.clearClue(idx); } - Clues hillclimb(Clues start, int limit) { + Clues hillclimb(Clues start, int clue_size, int limit) { var best = start; - var bestF = maskFitness(best); + var bestF = maskFitness(best, clue_size); var fails = 0; while (fails < limit) { cache.from(best); var cand = mutate(best); - var f = maskFitness(cand); + var f = maskFitness(cand, clue_size); if (f < bestF) { best = cand; bestF = f; @@ -674,32 +673,32 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { return best; } - public Clues generateMask(int popSize, int gens, int pairs) { + public Clues generateMask(int clueSize, int popSize, int gens, int offspring) { class GridAndFit { Clues grid; long fite = -1; GridAndFit(Clues grid) { this.grid = grid; } long fit() { - if (fite == -1) this.fite = maskFitness(grid); + if (fite == -1) this.fite = maskFitness(grid, clueSize); return this.fite; } } - if (Main.VERBOSE) System.out.println("generateMask init pop: " + popSize); + if (Main.VERBOSE) System.out.println("generateMask init pop: " + popSize + " clueSize: " + clueSize); var pop = new ArrayList(); for (var i = 0; i < popSize; i++) { - pop.add(new GridAndFit(hillclimb(randomMask(), 180))); + pop.add(new GridAndFit(hillclimb(randomMask(clueSize), clueSize, 180))); } for (var gen = 0; gen < gens; gen++) { if (Thread.currentThread().isInterrupted()) break; var children = new ArrayList(); - for (var k = 0; k < pairs; k++) { + for (var k = 0; k < offspring; k++) { var p1 = pop.get(rng.randint(0, pop.size() - 1)); var p2 = pop.get(rng.randint(0, pop.size() - 1)); var child = crossover(p1.grid, p2.grid); - children.add(new GridAndFit(hillclimb(child, 70))); + children.add(new GridAndFit(hillclimb(child, clueSize, 70))); } pop.addAll(children); @@ -707,7 +706,7 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) { var next = new ArrayList(); for (var cand : pop) { - if (next.size() >= popSize) break; + if (next.size() >= offspring) break; var ok = true; for (var kept : next) { if (cand.grid.similarity(kept.grid) > 0.92) { diff --git a/src/test/java/puzzle/MainTest.java b/src/test/java/puzzle/MainTest.java index d03d6e4..940a714 100644 --- a/src/test/java/puzzle/MainTest.java +++ b/src/test/java/puzzle/MainTest.java @@ -38,7 +38,9 @@ public class MainTest { static final int OFF_2_3 = Grid.offset(2, 3); static final Opts opts = new Main.Opts() {{ this.seed = 12348; + this.clueSize = 4; this.pop = 4; // Tiny population + this.offspring = 18; this.gens = 20; // Very few generations this.minSimplicity = 0; this.threads = 1; @@ -159,19 +161,19 @@ public class MainTest { @Test void testMaskerCreation() { var swe = new SwedishGenerator(new Rng(12348), new int[STACK_SIZE], Clues.createEmpty()); - var mask = swe.generateMask(opts.pop, opts.gens, Math.max(opts.pop, (int) Math.floor(opts.pop * 1.5))); + var mask = swe.generateMask(opts.clueSize, opts.pop, opts.gens, opts.offspring); val clued = new Clued(mask); val test = clued.gridToString(); - val RESULT = "001 \n" + - " 3 3\n" + + val RESULT = "1 \n" + + " \n" + " 3\n" + - " 3\n" + - " 21 1 \n" + - " 3 \n" + - "221 1 \n" + - "1 22"; + " \n" + + " \n" + + "1 \n" + + " \n" + + " 3"; - Assertions.assertEquals(18, clued.clueCount(), "Found seed changed"); + Assertions.assertEquals(4, clued.clueCount(), "Found seed changed"); Assertions.assertEquals(RESULT, test, "Found seed changed"); } @Test diff --git a/src/test/java/puzzle/SwedishGeneratorTest.java b/src/test/java/puzzle/SwedishGeneratorTest.java index 7d9f7ca..89b4537 100644 --- a/src/test/java/puzzle/SwedishGeneratorTest.java +++ b/src/test/java/puzzle/SwedishGeneratorTest.java @@ -241,12 +241,12 @@ public class SwedishGeneratorTest { var gen = new SwedishGenerator(new Rng(0), new int[STACK_SIZE], Clues.createEmpty()); var grid = Clues.createEmpty(); // Empty grid should have high penalty (no slots) - var f1 = gen.maskFitness(grid); + var f1 = gen.maskFitness(grid, 18); assertTrue(f1 >= 1_000_000_000L); // Add a slot grid.setClue(OFF_0_0, D_BYTE_2); - var f2 = gen.maskFitness(grid); + var f2 = gen.maskFitness(grid, 18); assertTrue(f2 < f1); } @@ -255,7 +255,7 @@ public class SwedishGeneratorTest { var rng = new Rng(42); var gen = new SwedishGenerator(rng, new int[STACK_SIZE], Clues.createEmpty()); - var g1 = gen.randomMask(); + var g1 = gen.randomMask(18); assertNotNull(g1); var g2 = gen.mutate(g1.deepCopyGrid()); @@ -264,7 +264,7 @@ public class SwedishGeneratorTest { assertNotNull(gen.crossover(g1, g2)); - var g4 = gen.hillclimb(g1, 10); + var g4 = gen.hillclimb(g1, 18, 10); assertNotNull(g4); } @@ -363,10 +363,10 @@ public class SwedishGeneratorTest { var gen = new SwedishGenerator(new Rng(42), new int[STACK_SIZE], Clues.createEmpty()); var grid = Clues.createEmpty(); // Empty grid: huge penalty - var fitEmpty = gen.maskFitness(grid); + var fitEmpty = gen.maskFitness(grid, 18); assertTrue(fitEmpty >= 1_000_000_000L); grid.setClue(0, D_BYTE_2); // Right from 0,0. Len 2 if 3x3. - var fitOne = gen.maskFitness(grid); + var fitOne = gen.maskFitness(grid, 18); assertTrue(fitOne < fitEmpty); } } \ No newline at end of file