From b0b10d356ae92f62ee26e2367da0ce5148e9a691 Mon Sep 17 00:00:00 2001 From: mike Date: Wed, 14 Jan 2026 03:03:34 +0100 Subject: [PATCH] introduce bitloops --- src/main/java/puzzle/SwedishGenerator.java | 186 +++++++++++++----- .../java/puzzle/SwedishGeneratorTest.java | 4 +- 2 files changed, 138 insertions(+), 52 deletions(-) diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index 745aab4..a606a06 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -1,5 +1,6 @@ package puzzle; +import lombok.AllArgsConstructor; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.experimental.Accessors; @@ -183,6 +184,92 @@ public record SwedishGenerator(Rng rng, int[] stack) { } } + @AllArgsConstructor + static class Clues { + + long lo, hi, vlo, vhi, rlo, rhi; + public static Clues createEmpty() { + return new Clues(0, 0, 0, 0, 0, 0); + } + public Clues deepCopyGrid() { return new Clues(lo, hi, vlo, vhi, rlo, rhi); } + boolean clueless(int idx) { + if (!isClue(idx)) return false; + clearClue(idx); + return true; + } + public boolean hasRoomForClue(long packed) { return (packed) != X && notClue(packed & 0x7FL) && notClue((packed >>> 7) & 0x7FL); } + + public void setClue(int ri, byte idx) { + if ((ri & 64) == 0) { + long mask = 1L << ri; + lo |= mask; + if ((idx & 1) != 0) vlo |= mask; else vlo &= ~mask; + if ((idx & 2) != 0) rlo |= mask; else rlo &= ~mask; + } else { + long mask = 1L << (ri & 63); + hi |= mask; + if ((idx & 1) != 0) vhi |= mask; else vhi &= ~mask; + if ((idx & 2) != 0) rhi |= mask; else rhi &= ~mask; + } + } + public byte digitAt(int idx) { + if ((idx & 64) == 0) { + int v = (int) ((vlo >>> idx) & 1L); + int r = (int) ((rlo >>> idx) & 1L); + return (byte) ((r << 1) | v); + } else { + int v = (int) ((vhi >>> (idx & 63)) & 1L); + int r = (int) ((rhi >>> (idx & 63)) & 1L); + return (byte) ((r << 1) | v); + } + } + public void clearClue(int idx) { + if ((idx & 64) == 0) { + long mask = ~(1L << idx); + lo &= mask; + vlo &= mask; + rlo &= mask; + } else { + long mask = ~(1L << (idx & 63)); + hi &= mask; + vhi &= mask; + rhi &= mask; + } + } + public boolean isClue(long index) { return ((index & 64) == 0) ? ((lo >>> index) & 1L) != X : ((hi >>> (index & 63)) & 1L) != X; } + public boolean isClue(int index) { return ((index & 64) == 0) ? ((lo >>> index) & 1L) != X : ((hi >>> (index & 63)) & 1L) != X; } + public boolean notClue(long index) { return ((index & 64) == 0) ? ((lo >>> index) & 1L) == X : ((hi >>> (index & 63)) & 1L) == X; } + public boolean notClue(int index) { return ((index & 64) == 0) ? ((lo >>> index) & 1L) == X : ((hi >>> (index & 63)) & 1L) == X; } + + public int clueCount() { return Long.bitCount(lo) + Long.bitCount(hi); } + public double similarity(Clues b) { + long matchLo = (~(lo ^ b.lo)) & (~lo | (~(vlo ^ b.vlo) & ~(rlo ^ b.rlo))); + long matchHi = (~(hi ^ b.hi)) & (~hi | (~(vhi ^ b.vhi) & ~(rhi ^ b.rhi))); + + long maskLo = (SIZE >= 64) ? -1L : (1L << SIZE) - 1; + long maskHi = (SIZE <= 64) ? 0L : (1L << (SIZE - 64)) - 1; + + return (Long.bitCount(matchLo & maskLo) + Long.bitCount(matchHi & maskHi)) / SIZED; + + + /* var same = 0; + for (int i = 0; i < SIZE; i++) if (digitAt(i) == b.digitAt(i)) same++; + return same / SIZED;*/ + } + public Grid toGrid() { + var grid = new Grid(new byte[SIZE], lo, hi); + for (var l = lo; l != X; l &= l - 1) { + int idx = Long.numberOfTrailingZeros(l); + grid.g[idx] = digitAt(idx); + } + for (var h = hi; h != X; h &= h - 1) { + int idx = 64 | Long.numberOfTrailingZeros(h); + grid.g[idx] = digitAt(idx); + } + return grid; + } + } + static class Grid { final byte[] g; @@ -218,30 +305,9 @@ public record SwedishGenerator(Rng rng, int[] stack) { boolean isClue(int index) { return ((index & 64) == 0) ? ((lo >>> index) & 1L) != X : ((hi >>> (index & 63)) & 1L) != X; } boolean notClue(long index) { return ((index & 64) == 0) ? ((lo >>> index) & 1L) == X : ((hi >>> (index & 63)) & 1L) == X; } boolean notClue(int index) { return ((index & 64) == 0) ? ((lo >>> index) & 1L) == X : ((hi >>> (index & 63)) & 1L) == X; } - boolean clueless(int idx) { - if ((idx & 64) == 0) { - val test = (1L << idx); - if ((test & lo) == X) return false; - g[idx] = DASH; - - lo &= ~test; - } else { - val test = (1L << (idx & 63)); - if ((test & hi) == X) return false; - g[idx] = DASH; - - hi &= ~test; - } - return true; - } - public double similarity(Grid b) { - var same = 0; - for (int i = 0; i < SIZE; i++) if (g[i] == b.g[i]) same++; - return same / SIZED; - } - int clueCount() { return Long.bitCount(lo) + Long.bitCount(hi); } - boolean hasRoomForClue(long packed) { return (packed) != X && notClue(packed & 0x7FL) && notClue((packed >>> 7) & 0x7FL); } + int clueCount() { return Long.bitCount(lo) + Long.bitCount(hi); } + void forEachSlot(SlotVisitor visitor) { for (var l = lo; l != X; l &= l - 1) processSlot(this, visitor, Long.numberOfTrailingZeros(l)); for (var h = hi; h != X; h &= h - 1) processSlot(this, visitor, 64 | Long.numberOfTrailingZeros(h)); @@ -402,7 +468,7 @@ public record SwedishGenerator(Rng rng, int[] stack) { } /// does not modify the grid - long maskFitness(final Grid grid) { + long maskFitness(final Clues grid) { long cHLo = 0L, cHHi = 0L, cVLo = 0L, cVHi = 0L; long lo_cl = grid.lo, hi_cl = grid.hi; @@ -413,6 +479,15 @@ public record SwedishGenerator(Rng rng, int[] stack) { for (long bits = (i == 0 ? lo_cl : hi_cl); bits != X; bits &= bits - 1) { int clueIdx = i | Long.numberOfTrailingZeros(bits); int key = Slot.packSlotDir(clueIdx, grid.digitAt(clueIdx)); + /// long clueBits = (i == 0 ? lo_cl : hi_cl); + /// long vBits = (i == 0 ? grid.vlo : grid.vhi); + /// long rBits = (i == 0 ? grid.rlo : grid.rhi); + /// for (long bits = clueBits; bits != X; bits &= bits - 1) { + /// long lsb = bits & -bits; + /// int clueIdx = i | Long.numberOfTrailingZeros(lsb); + /// int v = (vBits & lsb) != 0 ? 1 : 0; + /// int r = (rBits & lsb) != 0 ? 1 : 0; + /// int key = Slot.packSlotDir(clueIdx, (r << 1) | v); long rLo = PATH_LO[key], rHi = PATH_HI[key]; long hLo = rLo & lo_cl, hHi = rHi & hi_cl; if (Slot.increasing(key)) { @@ -531,8 +606,8 @@ public record SwedishGenerator(Rng rng, int[] stack) { return penalty; } - Grid randomMask() { - var g = Grid.createEmpty(); + SwedishGenerator.Clues randomMask() { + var g = Clues.createEmpty(); for (int placed = 0, guard = 0, idx; placed < TARGET_CLUES && guard < 4000; guard++) { idx = rng.randint(0, SIZE_MIN_1); if (g.isClue(idx)) continue; @@ -544,7 +619,7 @@ public record SwedishGenerator(Rng rng, int[] stack) { } return g; } - Grid mutate(Grid grid) { + Clues mutate(Clues grid) { var g = grid.deepCopyGrid(); int ri; var bytes = MUTATE_RI[rng.randint(0, SIZE_MIN_1)]; @@ -557,38 +632,49 @@ public record SwedishGenerator(Rng rng, int[] stack) { } return g; } - Grid crossover(Grid a, Grid other) { + Clues crossover(Clues a, Clues other) { var out = a.deepCopyGrid(); var theta = rng.nextFloat() * Math.PI; var nc = Math.cos(theta); var nr = Math.sin(theta); - long bo0 = out.lo, bo1 = out.hi; for (var rci : IT) { int i = rci.i(); if ((rci.cross_r()) * nc + (rci.cross_c()) * nr < 0) { - byte ch = other.g[i]; - if (out.g[i] != ch) { - out.g[i] = ch; + var ch = other.digitAt(i); + if (out.digitAt(i) != ch) { if (other.isClue(i)) { - if ((i & 64) == 0) bo0 |= (1L << i); - else bo1 |= (1L << (i & 63)); + out.setClue(i, ch); } else { - if ((i & 64) == 0) bo0 &= ~(1L << i); - else bo1 &= ~(1L << (i & 63)); + out.clearClue(i); } } } } - out.lo = bo0; - out.hi = bo1; + + // long maskLo = 0, maskHi = 0; + // for (var rci : IT) { + // if ((rci.cross_r()) * nc + (rci.cross_c()) * nr < 0) { + // int i = rci.i(); + // if (i < 64) maskLo |= (1L << i); + // else maskHi |= (1L << (i - 64)); + // } + // } + // + // out.lo = (out.lo & ~maskLo) | (other.lo & maskLo); + // out.hi = (out.hi & ~maskHi) | (other.hi & maskHi); + // out.vlo = (out.vlo & ~maskLo) | (other.vlo & maskLo); + // out.vhi = (out.vhi & ~maskHi) | (other.vhi & maskHi); + // out.rlo = (out.rlo & ~maskLo) | (other.rlo & maskLo); + // out.rhi = (out.rhi & ~maskHi) | (other.rhi & maskHi); + for (var lo = out.lo; lo != X; lo &= lo - 1L) clearClues(out, Long.numberOfTrailingZeros(lo)); for (var hi = out.hi; hi != X; hi &= hi - 1L) clearClues(out, 64 | Long.numberOfTrailingZeros(hi)); return out; } - public static void clearClues(Grid out, int idx) { if (!out.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotDir(idx, out.digitAt(idx))])) out.clearClue(idx); } + public static void clearClues(Clues out, int idx) { if (!out.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotDir(idx, out.digitAt(idx))])) out.clearClue(idx); } - Grid hillclimb(Grid start, int limit) { + Clues hillclimb(Clues start, int limit) { var best = start; var bestF = maskFitness(best); var fails = 0; @@ -607,12 +693,12 @@ public record SwedishGenerator(Rng rng, int[] stack) { return best; } - public Grid generateMask(int popSize, int gens, int pairs) { + public SwedishGenerator.Grid generateMask(int popSize, int gens, int pairs) { class GridAndFit { - Grid grid; - long fite = -1; - GridAndFit(Grid grid) { this.grid = grid; } + Clues grid; + long fite = -1; + GridAndFit(Clues grid) { this.grid = grid; } long fit() { if (fite == -1) this.fite = maskFitness(grid); return this.fite; @@ -659,10 +745,10 @@ public record SwedishGenerator(Rng rng, int[] stack) { var x = pop.get(i); if (x.fit() < best.fit()) best = x; } - return best.grid; + return best.grid.toGrid(); } - static long patternForSlot(Grid grid, Slot s) { + static long patternForSlot(Grid grid, SwedishGenerator.Slot s) { if ((s.lo & ~grid.lo) == 0 && (s.hi & ~grid.hi) == 0) return 0; long p = 0; if (s.increasing()) { @@ -769,7 +855,7 @@ public record SwedishGenerator(Rng rng, int[] stack) { return true; } - static int[] candidateInfoForPattern(long[] res, long pattern, DictEntry entry, int lenb) { + static int[] candidateInfoForPattern(long[] res, long pattern, SwedishGenerator.DictEntry entry, int lenb) { int numLongs = entry.numlong; boolean first = true; @@ -841,7 +927,7 @@ public record SwedishGenerator(Rng rng, int[] stack) { for (int i = 0; i < slots.length; i++) slotScores[i] = slotScore(count, slots[i]); } - public static FillResult fillMask(Rng rng, Grid mask, DictEntry[] dictIndex) { + public static SwedishGenerator.FillResult fillMask(SwedishGenerator.Rng rng, Grid mask, DictEntry[] dictIndex) { val multiThreaded = Thread.currentThread().getName().contains("pool"); val NO_LOG = (!Main.VERBOSE || multiThreaded); val grid = mask.deepCopyGrid(); @@ -885,7 +971,7 @@ public record SwedishGenerator(Rng rng, int[] stack) { System.out.print("\r" + Strings.padRight(msg, 120)); System.out.flush(); } - Pick chooseMRV() { + SwedishGenerator.Pick chooseMRV() { Slot best = null; for (int i = 0, count, count2 = -1, bestScore = -1, n = TOTAL; i < n; i++) { var s = slots[i]; @@ -993,7 +1079,7 @@ public record SwedishGenerator(Rng rng, int[] stack) { var ok = solver.backtrack(0); // final progress line - var res = new FillResult(ok, new Gridded(grid), assigned, new FillStats(solver.nodes, solver.backtracks, (System.currentTimeMillis() - t0) / 1000.0, solver.lastMRV)); + var res = new FillResult(ok, new Gridded(grid), assigned, new SwedishGenerator.FillStats(solver.nodes, solver.backtracks, (System.currentTimeMillis() - t0) / 1000.0, solver.lastMRV)); if (!multiThreaded) { System.out.print("\r" + Strings.padRight("", 120) + "\r"); System.out.flush(); diff --git a/src/test/java/puzzle/SwedishGeneratorTest.java b/src/test/java/puzzle/SwedishGeneratorTest.java index 0e10686..c2e3689 100644 --- a/src/test/java/puzzle/SwedishGeneratorTest.java +++ b/src/test/java/puzzle/SwedishGeneratorTest.java @@ -285,7 +285,7 @@ public class SwedishGeneratorTest { @Test void testMaskFitnessBasic() { var gen = new SwedishGenerator(new Rng(0), new int[STACK_SIZE]); - var grid = Grid.createEmpty(); + var grid = Clues.createEmpty(); // Empty grid should have high penalty (no slots) var f1 = gen.maskFitness(grid); assertTrue(f1 >= 1_000_000_000L); @@ -426,7 +426,7 @@ public class SwedishGeneratorTest { @Test void testMaskFitnessDetailed() { var gen = new SwedishGenerator(new Rng(42), new int[STACK_SIZE]); - var grid = Grid.createEmpty(); + var grid = Clues.createEmpty(); // Empty grid: huge penalty var fitEmpty = gen.maskFitness(grid); assertTrue(fitEmpty >= 1_000_000_000L);