introduce bitloops

This commit is contained in:
mike
2026-01-12 02:02:01 +01:00
parent 84ba4c9c63
commit dd28151d49
3 changed files with 69 additions and 44 deletions

View File

@@ -16,9 +16,11 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.stream.IntStream;
import static java.nio.charset.StandardCharsets.*;
@@ -37,7 +39,7 @@ public record SwedishGenerator(Rng rng) {
public CandidateInfo(int n) { this(null, n); }
}
static final CandidateInfo[] CANDIDATES = IntStream.range(0, 10192 << 2).mapToObj(CandidateInfo::new).toArray(CandidateInfo[]::new);
// static final CandidateInfo[] CANDIDATES = IntStream.range(0, 10192 << 2).mapToObj(CandidateInfo::new).toArray(CandidateInfo[]::new);
//@formatter:off
@FunctionalInterface interface SlotVisitor { void visit(int key, long packedPos, int len); }
@@ -108,7 +110,7 @@ public record SwedishGenerator(Rng rng) {
public static record FillResult(boolean ok,
Gridded grid,
HashMap<Integer, Lemma> clueMap,
Map<Integer, Lemma> clueMap,
FillStats stats) {
public void calcSimpel() {
@@ -191,7 +193,7 @@ public record SwedishGenerator(Rng rng) {
if (idx < 64) lo |= (1L << idx);
else hi |= (1L << (idx & 63));
}
void clear(int idx) { g[idx] = DASH; }
void clearletter(int idx) { g[idx] = DASH; }
void clearClue(int idx) {
g[idx] = DASH;
if (idx < 64) lo &= ~(1L << idx);
@@ -246,7 +248,7 @@ public record SwedishGenerator(Rng rng) {
boolean hasRoomForClue(long packed) { return (packed & GT_1_OFFSET_53_BIT) != X && notClue(packed & 0x7FL) && notClue((packed >>> 7) & 0x7FL); }
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));
for (var h = hi; h != X; h &= h - 1) processSlot(this, visitor, 64 + Long.numberOfTrailingZeros(h));
}
}
@@ -352,17 +354,19 @@ public record SwedishGenerator(Rng rng) {
static record Slot(int key, long packedPos) {
static final int BIT_FOR_DIR = 3;
static Slot from(int key, long packedPos, int len) { return new Slot(key, packedPos | ((long) len << 56)); }
void undoPlace(Grid grid, int mask) { for (int i = 0, len = len(); i < len; i++) if ((mask & (1L << i)) != 0) grid.clear(pos(i)); }
void undoPlace(Grid grid, int mask) { for (int i = 0, len = len(); i < len; i++) if ((mask & (1L << i)) != 0) grid.clearletter(pos(i)); }
public int len() { return (int) (packedPos >>> 56); }
public int clueR() { return Grid.r((key >>> 4)); }
public int clueIndex() { return key >>> 4; }
public int clueC() { return Grid.c((key >>> 4)); }
public int dir() { return key & 15; }
public int clueR() { return Grid.r((key >>> BIT_FOR_DIR)); }
public int clueIndex() { return key >>> BIT_FOR_DIR; }
public int clueC() { return Grid.c((key >>> BIT_FOR_DIR)); }
public int dir() { return key & 7; }
public boolean horiz() { return horiz(key); }
public int pos(int i) { return offset(packedPos, i); }
public static boolean horiz(int key) { return (key & 1) == 0/*((key & 15) & 1) == 0*/; }
public static int offset(long packedPos, int i) { return (int) ((packedPos >> (i * 7)) & 127); }
public static int packSlotDir(int idx, int d) { return (idx << BIT_FOR_DIR) | d; }
}
private static void processSlot(Grid grid, SlotVisitor visitor, int idx) {
@@ -376,7 +380,7 @@ public record SwedishGenerator(Rng rng) {
packedPos |= iidx << offset;
}
if (k > 0) {
visitor.visit((idx << 4) | d, packedPos, k);
visitor.visit(Slot.packSlotDir(idx, d), packedPos, k);
}
}
@@ -410,38 +414,39 @@ public record SwedishGenerator(Rng rng) {
var covV = ctx.covV;
Arrays.fill(covH, 0, SIZE, 0);
Arrays.fill(covV, 0, SIZE, 0);
int clueIdx;
long lo_cl = grid.lo, hi_cl = grid.hi;
long penalty = 0L;
long lo_cl = grid.lo, hi_cl = grid.hi;
long penalty = (((long) Math.abs(grid.clueCount() - TARGET_CLUES)) << 3);
boolean hasSlots = false;
for (int i = 0; i < 65; i += 64) {
for (long bits = (i == 0 ? lo_cl : hi_cl); bits != X; bits &= bits - 1) {
clueIdx = i + Long.numberOfTrailingZeros(bits);
var d = grid.digitAt(clueIdx);
var nbrs16 = OFFSETS[d];
long packed = nbrs16.path()[clueIdx];
int n = (int) (packed >>> 56) * 7, k, idx;
var horiz = Slot.horiz(d) ? covH : covV;
int clueIdx = i + Long.numberOfTrailingZeros(bits);
var d = grid.digitAt(clueIdx);
var nbrs16 = OFFSETS[d];
long packed = nbrs16.path()[clueIdx];
int n = (int) (packed >>> 56) * 7, k, idx;
var horiz = Slot.horiz(d) ? covH : covV;
for (k = 0; k < n && k < MAX_WORD_LENGTH7; k += 7) {
idx = (int) ((packed >>> (k)) & 0x7F);
if (grid.isClue(idx)) break;
horiz[idx] += 1;
}
if (k > 0) {
hasSlots = true;
if (k < MIN_LEN7) penalty += 8000;
penalty |= 1;
}
}
}
if ((penalty & 1) == 0) return 1_000_000_000L;
penalty = (penalty & ~3L) + (((long) Math.abs(grid.clueCount() - TARGET_CLUES)) << 3);
if (!hasSlots) return 1_000_000_000L;
var seen = ctx.seen;
var stack = ctx.stack;
seen.clear();
for (int i = 0; i < 65; i += 64) {
for (long bits = (i == 0 ? lo_cl : hi_cl); bits != X; bits &= bits - 1) {
clueIdx = i + Long.numberOfTrailingZeros(bits);
int clueIdx = i + Long.numberOfTrailingZeros(bits);
if (seen.get(clueIdx)) continue;
int size = 0;
stack[0] = clueIdx;
@@ -462,8 +467,8 @@ public record SwedishGenerator(Rng rng) {
for (int i = 0; i < 65; i += 64) {
long bits = (i == 0 ? ~lo_cl : (~hi_cl & 0xFFL));
for (; bits != X; bits &= bits - 1) {
clueIdx = i + Long.numberOfTrailingZeros(bits);
var rci = IT[clueIdx];
int clueIdx = i + Long.numberOfTrailingZeros(bits);
var rci = IT[clueIdx];
if ((4 - rci.nbrCount()) + Long.bitCount(rci.n1() & lo_cl) + Long.bitCount(rci.n2() & hi_cl) >= 3) penalty += 400;
var h = covH[clueIdx];
var v = covV[clueIdx];
@@ -601,9 +606,14 @@ public record SwedishGenerator(Rng rng) {
if (Main.VERBOSE && (gen & 15) == 15) System.out.println(" gen " + gen + "/" + gens + " bestFitness=" + pop.get(0).fit());
}
pop.sort(Comparator.comparingLong(GridAndFit::fit));
return pop.get(0).grid;
GridAndFit best = pop.get(0);
for (int i = 1; i < pop.size(); i++) {
var x = pop.get(i);
if (x.fit() < best.fit()) best = x;
}
//pop.sort(Comparator.comparingLong(GridAndFit::fit));
//return pop.get(0).grid;
return best.grid;
}
static void patternForSlot(Grid grid, Slot s, byte[] pat) {
byte ch;
@@ -631,7 +641,7 @@ public record SwedishGenerator(Rng rng) {
} else if (cur != ch) {
for (var j = 0; j < i; j++) {
if ((mask & (1 << j)) != 0) {
grid.clear(s.pos(j));
grid.clearletter(s.pos(j));
}
}
return false;
@@ -655,7 +665,8 @@ public record SwedishGenerator(Rng rng) {
}
if (listCount == 0) {
return CANDIDATES[entry.words.length];
//return CANDIDATES[entry.words.length];
return new CandidateInfo(null, entry.words.length);
}
// Sort constraints by size to optimize intersection
@@ -693,9 +704,10 @@ public record SwedishGenerator(Rng rng) {
val multiThreaded = Thread.currentThread().getName().contains("pool");
val grid = mask.deepCopyGrid();
val used = new Bit1029();
val assigned = new HashMap<Integer, Lemma>();
val ctx = CTX.get();
val count = ctx.cellCount;
// val assigned = new HashMap<Integer, Lemma>();
Lemma[] assigned = new Lemma[1024];
val ctx = CTX.get();
val count = ctx.cellCount;
Arrays.fill(count, 0, SIZE, 0);
val slots = extractSlots(grid);
@@ -716,8 +728,10 @@ public record SwedishGenerator(Rng rng) {
var now = System.currentTimeMillis();
if ((now - lastLog) < LOG_EVERY_MS) return;
lastLog = (now);
var done = assigned.size();
var done = 0;
for (var lemma : assigned) {
if (lemma != null) done++;
}
var pct = (TOTAL == 0) ? 100 : (int) Math.floor((done / (double) TOTAL) * 100);
var filled = Math.min(BAR_LEN, (int) Math.floor((pct / 100.0) * BAR_LEN));
var bar = "[" + "#".repeat(filled) + "-".repeat(BAR_LEN - filled) + "]";
@@ -737,7 +751,7 @@ public record SwedishGenerator(Rng rng) {
int bestScore = -1;
for (int i = 0, n = slots.size(); i < n; i++) {
var s = slots.get(i);
if (assigned.containsKey(s.key())) continue;
if (assigned[s.key()] != null) continue;
var entry = dictIndex[s.len()];
if (entry == null) return PICK_NOT_DONE;
patternForSlot(grid, s, ctx.pattern);
@@ -798,11 +812,11 @@ public record SwedishGenerator(Rng rng) {
if (!placeWord(grid, s, w, ctx.undo, depth)) continue;
used.set(w.index);
assigned.put(k, w);
assigned[k] = w;
if (backtrack(depth + 1)) return true;
assigned.remove(k);
assigned[k] = null;
used.clear(w.index);
s.undoPlace(grid, ctx.undo[depth]);
}
@@ -827,11 +841,11 @@ public record SwedishGenerator(Rng rng) {
if (!placeWord(grid, s, w, ctx.undo, depth)) continue;
used.set(w.index);
assigned.put(k, w);
assigned[k] = w;
if (backtrack(depth + 1)) return true;
assigned.remove(k);
assigned[k] = null;
used.clear(w.index);
s.undoPlace(grid, ctx.undo[depth]);
}
@@ -852,14 +866,20 @@ public record SwedishGenerator(Rng rng) {
}
stats.seconds = (System.currentTimeMillis() - t0) / 1000.0;
var res = new FillResult(ok, new Gridded(grid), assigned, stats);
Map<Integer, Lemma> lemmaMap = new HashMap<>();
for (var i = 0; i < assigned.length; i++) {
if (assigned[i] != null) {
lemmaMap.put(i, assigned[i]);
}
}
var res = new FillResult(ok, new Gridded(grid), lemmaMap, stats);
// print a final progress line
if (Main.VERBOSE && !multiThreaded) {
System.out.println(
String.format(Locale.ROOT,
"[######################] %d/%d slots | nodes=%d | backtracks=%d | mrv=%d | %.1fs",
assigned.size(), TOTAL, stats.nodes, stats.backtracks, stats.lastMRV, stats.seconds
lemmaMap.size(), TOTAL, stats.nodes, stats.backtracks, stats.lastMRV, stats.seconds
)
);
}

View File

@@ -167,7 +167,7 @@ public class MainTest {
// Regression baseline for seed search starting at 12347, pop 4, gens 20
Assertions.assertEquals(12348, foundSeed, "Found seed changed");
Assertions.assertEquals(22, res.filled().clueMap().size(), "Number of assigned words changed");
Assertions.assertEquals(Lemma.pack(new byte[]{ 'I', 'E', 'M', 'A', 'N', 'D', 'S' }), res.filled().clueMap().get(515).word());
Assertions.assertEquals(Lemma.pack(new byte[]{ 'I', 'E', 'M', 'A', 'N', 'D', 'S' }), res.filled().clueMap().get(259).word());
Assertions.assertEquals(648985643903632391L, res.filled().grid().grid().lo);
Assertions.assertEquals(140L, res.filled().grid().grid().hi);
}

View File

@@ -135,8 +135,12 @@ public class SwedishGeneratorTest {
@Test
void testSlot() {
System.out.println("[DEBUG_LOG] Slot.BIT_FOR_DIR = " + Slot.BIT_FOR_DIR);
// key = (r << 8) | (c << 4) | d
var key = (Grid.offset(2, 3) << 4) | 5;
var offset = Grid.offset(2, 3);
System.out.println("[DEBUG_LOG] Grid.offset(2, 3) = " + offset);
var key = (offset << Slot.BIT_FOR_DIR) | 5;
System.out.println("[DEBUG_LOG] key = " + key);
long packedPos = 0;
// pos 0: (2, 5)
packedPos |= Grid.offset(2, 5);
@@ -146,6 +150,7 @@ public class SwedishGeneratorTest {
packedPos |= (long) Grid.offset(4, 5) << 14;
var s = Slot.from(key, packedPos, 3);
System.out.println("[DEBUG_LOG] s.dir() = " + s.dir());
assertEquals(2, s.clueR());
assertEquals(3, s.clueC());
assertEquals(5, s.dir());