From 107dfab0c7a839eddab0c788d009c6f688ff9173 Mon Sep 17 00:00:00 2001 From: mike Date: Wed, 14 Jan 2026 02:18:40 +0100 Subject: [PATCH] introduce bitloops --- src/main/java/puzzle/Main.java | 4 +- src/main/java/puzzle/SwedishGenerator.java | 54 ++++++++++--------- src/test/java/puzzle/ExportFormatTest.java | 4 +- .../java/puzzle/SwedishGeneratorTest.java | 28 +++++----- 4 files changed, 44 insertions(+), 46 deletions(-) diff --git a/src/main/java/puzzle/Main.java b/src/main/java/puzzle/Main.java index a6645bd..0a04b3f 100644 --- a/src/main/java/puzzle/Main.java +++ b/src/main/java/puzzle/Main.java @@ -356,9 +356,9 @@ public class Main { } static PuzzleResult _attempt(Rng rng, Dict dict, Opts opts) { TOTAL_ATTEMPTS.incrementAndGet(); - var swe = new SwedishGenerator(rng); val stack = new int[STACK_SIZE]; - var mask = swe.generateMask(stack, opts.pop, opts.gens, Math.max(opts.pop, (int) Math.floor(opts.pop * 1.5))); + var swe = new SwedishGenerator(rng, stack); + var mask = swe.generateMask(opts.pop, opts.gens, Math.max(opts.pop, (int) Math.floor(opts.pop * 1.5))); var filled = fillMask(rng, mask, dict.index()); TOTAL_NODES.addAndGet(filled.stats().nodes); diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index 0a8c1d7..745aab4 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -29,6 +29,7 @@ import static java.nio.charset.StandardCharsets.*; * D) separate clue into three long pairs of hi and lo, (v,h,r) so we do not store the clue anymore in the grid array at all * E) store letter-set also in a long pair so we can use it in path determinations * F) pre-determine random clue arrangements, so they do not involve impossible clue's such as bottom at last or the line before that and such to all sides + * G) Check 3wall, the current implementation may be faster, but the accuracy is lower, degrade performance on the filler */ /** @@ -39,7 +40,7 @@ import static java.nio.charset.StandardCharsets.*; * java SwedishGenerator [--seed N] [--pop N] [--gens N] [--tries N] [--words word-list.txt] */ @SuppressWarnings("ALL") -public record SwedishGenerator(Rng rng) { +public record SwedishGenerator(Rng rng, int[] stack) { record CandidateInfo(int[] indices, int count) { } @@ -68,7 +69,7 @@ public record SwedishGenerator(Rng rng) { //72 << 3; static final int CLUE_INDEX_MAX_SIZE = (288 | 3) + 1; static int clamp(int x, int a, int b) { return Math.max(a, Math.min(b, x)); } - record Pick(Slot slot, CandidateInfo info, boolean done) { } + record Pick(Slot slot, int[] indices, int count) { } // 0b11 //0b00 // 0b01 @@ -96,8 +97,8 @@ public record SwedishGenerator(Rng rng) { } } - static final Pick PICK_DONE = new Pick(null, null, true); - static final Pick PICK_NOT_DONE = new Pick(null, null, false); + static final Pick PICK_DONE = null;//new Pick(null, null, 0, true); + static final Pick PICK_NOT_DONE = new Pick(null, null, 0); @RequiredArgsConstructor @Getter @@ -251,7 +252,7 @@ public record SwedishGenerator(Rng rng) { } } - static record DictEntry(long[] words, long[][] posBitsets, CandidateInfo empty, int length, int numlong) { } + static record DictEntry(long[] words, long[][] posBitsets, int length, int numlong) { } static int LEMMA_COUNTER = 0; @@ -318,7 +319,7 @@ public record SwedishGenerator(Rng rng) { } } } - return new DictEntry(words, bitsets, new CandidateInfo(null, words.length), words.length, (words.length + 63) >>> 6); + return new DictEntry(words, bitsets, words.length, (words.length + 63) >>> 6); }).toArray(DictEntry[]::new), Arrays.stream(index).mapToInt(i -> i.words().size()).sum()); } @@ -400,7 +401,8 @@ public record SwedishGenerator(Rng rng) { return slots; } - long maskFitness(Grid grid, int[] stack) { + /// does not modify the grid + long maskFitness(final Grid grid) { long cHLo = 0L, cHHi = 0L, cVLo = 0L, cVHi = 0L; long lo_cl = grid.lo, hi_cl = grid.hi; @@ -555,7 +557,7 @@ public record SwedishGenerator(Rng rng) { } return g; } - Grid crossover(Grid a, Grid b) { + Grid crossover(Grid a, Grid other) { var out = a.deepCopyGrid(); var theta = rng.nextFloat() * Math.PI; var nc = Math.cos(theta); @@ -565,10 +567,10 @@ public record SwedishGenerator(Rng rng) { for (var rci : IT) { int i = rci.i(); if ((rci.cross_r()) * nc + (rci.cross_c()) * nr < 0) { - byte ch = b.g[i]; + byte ch = other.g[i]; if (out.g[i] != ch) { out.g[i] = ch; - if (b.isClue(i)) { + if (other.isClue(i)) { if ((i & 64) == 0) bo0 |= (1L << i); else bo1 |= (1L << (i & 63)); } else { @@ -586,14 +588,14 @@ public record SwedishGenerator(Rng rng) { } public static void clearClues(Grid out, int idx) { if (!out.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotDir(idx, out.digitAt(idx))])) out.clearClue(idx); } - Grid hillclimb(int[] stack, Grid start, int limit) { + Grid hillclimb(Grid start, int limit) { var best = start; - var bestF = maskFitness(best, stack); + var bestF = maskFitness(best); var fails = 0; while (fails < limit) { var cand = mutate(best); - var f = maskFitness(cand, stack); + var f = maskFitness(cand); if (f < bestF) { best = cand; bestF = f; @@ -605,21 +607,21 @@ public record SwedishGenerator(Rng rng) { return best; } - public Grid generateMask(int[] stack, int popSize, int gens, int pairs) { + public Grid generateMask(int popSize, int gens, int pairs) { class GridAndFit { Grid grid; long fite = -1; GridAndFit(Grid grid) { this.grid = grid; } long fit() { - if (fite == -1) this.fite = maskFitness(grid, stack); + if (fite == -1) this.fite = maskFitness(grid); return this.fite; } } if (Main.VERBOSE) System.out.println("generateMask init pop: " + popSize); var pop = new ArrayList(); for (var i = 0; i < popSize; i++) { - pop.add(new GridAndFit(hillclimb(stack, randomMask(), 180))); + pop.add(new GridAndFit(hillclimb(randomMask(), 180))); } for (var gen = 0; gen < gens; gen++) { @@ -630,7 +632,7 @@ public record SwedishGenerator(Rng rng) { 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(stack, child, 70))); + children.add(new GridAndFit(hillclimb(child, 70))); } pop.addAll(children); @@ -767,7 +769,7 @@ public record SwedishGenerator(Rng rng) { return true; } - static CandidateInfo candidateInfoForPattern(long[] res, long pattern, DictEntry entry, int lenb) { + static int[] candidateInfoForPattern(long[] res, long pattern, DictEntry entry, int lenb) { int numLongs = entry.numlong; boolean first = true; @@ -801,7 +803,7 @@ public record SwedishGenerator(Rng rng) { } } - return new CandidateInfo(indices, count); + return indices; } static int candidateCountForPattern(final long[] res, final long pattern, final DictEntry entry, final int lenb) { @@ -906,8 +908,8 @@ public record SwedishGenerator(Rng rng) { if (best == null) return PICK_DONE; var pattern = patternForSlot(grid, best); var index = dictIndex[best.length()]; - if (pattern == X) return new Pick(best, index.empty, false); - return new Pick(best, candidateInfoForPattern(bitset, pattern, index, best.length()), false); + if (pattern == X) return new Pick(best, null, index.length); + return new Pick(best, candidateInfoForPattern(bitset, pattern, index, best.length()), index.length); } boolean backtrack(int depth) { if (Thread.currentThread().isInterrupted()) return false; @@ -916,21 +918,21 @@ public record SwedishGenerator(Rng rng) { if (20_000 > 0 && (System.currentTimeMillis() - t0) > 20_000) return false; var pick = chooseMRV(); - if (pick.done) return true; + if (pick == PICK_DONE) return true; if (pick.slot == null) { backtracks++; return false; } - val info = pick.info; - lastMRV = info.count; + val info = pick.indices; + lastMRV = pick.count; if (!NO_LOG) renderProgress(); val s = pick.slot; val k = s.key; val entry = dictIndex[s.length()]; - if (info.indices != null && info.indices.length > 0) { - var idxs = info.indices; + if (info != null && info.length > 0) { + var idxs = info; var L = idxs.length; var tries = Math.min(MAX_TRIES_PER_SLOT, L); diff --git a/src/test/java/puzzle/ExportFormatTest.java b/src/test/java/puzzle/ExportFormatTest.java index bcbff7d..0ca19cf 100644 --- a/src/test/java/puzzle/ExportFormatTest.java +++ b/src/test/java/puzzle/ExportFormatTest.java @@ -26,7 +26,7 @@ public class ExportFormatTest { @Test void testExportFormatFromFilled() { - var swe = new SwedishGenerator(new Rng(0)); + var swe = new SwedishGenerator(new Rng(0), new int[STACK_SIZE]); var grid = Grid.createEmpty(); // Place a RIGHT clue at (0,0) @@ -84,7 +84,7 @@ public class ExportFormatTest { @Test void testExportFormatEmpty() { - var swe = new SwedishGenerator(new Rng(0)); + var swe = new SwedishGenerator(new Rng(0), new int[STACK_SIZE]); var grid = Grid.createEmpty(); var fillResult = new FillResult(true, new Gridded(grid), new long[300], new FillStats(0, 0, 0, 0)); var puzzleResult = new PuzzleResult(swe, null, null, fillResult); diff --git a/src/test/java/puzzle/SwedishGeneratorTest.java b/src/test/java/puzzle/SwedishGeneratorTest.java index 601c821..0e10686 100644 --- a/src/test/java/puzzle/SwedishGeneratorTest.java +++ b/src/test/java/puzzle/SwedishGeneratorTest.java @@ -261,13 +261,12 @@ public class SwedishGeneratorTest { // Pattern "APP--" for length 5 var info = candidateInfoForPattern(Context.get().bitset(), packPattern("APP"), dict.index()[5], 5); - assertEquals(2, info.count()); - assertNotNull(info.indices()); + assertEquals(2, info.length); + assertNotNull(info); } @Test void testForEachSlotAndExtractSlots() { - var gen = new SwedishGenerator(new Rng(0)); var grid = Grid.createEmpty(); // 3x3 grid (Config.PUZZLE_ROWS/COLS are 3 in test env) // Set CLUE_RIGHT at OFF_0_0 @@ -285,23 +284,22 @@ public class SwedishGeneratorTest { @Test void testMaskFitnessBasic() { - var gen = new SwedishGenerator(new Rng(0)); - var grid = Grid.createEmpty(); - var stack = new int[STACK_SIZE]; + var gen = new SwedishGenerator(new Rng(0), new int[STACK_SIZE]); + var grid = Grid.createEmpty(); // Empty grid should have high penalty (no slots) - var f1 = gen.maskFitness(grid, stack); + var f1 = gen.maskFitness(grid); assertTrue(f1 >= 1_000_000_000L); // Add a slot grid.setClue(OFF_0_0, D_BYTE_2); - var f2 = gen.maskFitness(grid, stack); + var f2 = gen.maskFitness(grid); assertTrue(f2 < f1); } @Test void testGeneticAlgorithmComponents() { var rng = new Rng(42); - var gen = new SwedishGenerator(rng); + var gen = new SwedishGenerator(rng, new int[STACK_SIZE]); var g1 = gen.randomMask(); assertNotNull(g1); @@ -312,8 +310,7 @@ public class SwedishGeneratorTest { assertNotNull(gen.crossover(g1, g2)); - val stack = new int[STACK_SIZE]; - var g4 = gen.hillclimb(stack, g1, 10); + var g4 = gen.hillclimb(g1, 10); assertNotNull(g4); } @@ -428,16 +425,15 @@ public class SwedishGeneratorTest { @Test void testMaskFitnessDetailed() { - var gen = new SwedishGenerator(new Rng(42)); - var grid = Grid.createEmpty(); - val stack = new int[STACK_SIZE]; + var gen = new SwedishGenerator(new Rng(42), new int[STACK_SIZE]); + var grid = Grid.createEmpty(); // Empty grid: huge penalty - var fitEmpty = gen.maskFitness(grid, stack); + var fitEmpty = gen.maskFitness(grid); assertTrue(fitEmpty >= 1_000_000_000L); // Grid with one short slot: still high penalty but less than empty grid.setClue(0, D_BYTE_2); // Right from 0,0. Len 2 if 3x3. - var fitOne = gen.maskFitness(grid, stack); + var fitOne = gen.maskFitness(grid); assertTrue(fitOne < fitEmpty); // Test penalty for TARGET_CLUES