introduce bitloops

This commit is contained in:
mike
2026-01-14 00:57:41 +01:00
parent 2430dbdfb9
commit 1d731334d9
6 changed files with 144 additions and 149 deletions

View File

@@ -163,17 +163,8 @@ public record Export() {
public record PuzzleResult(SwedishGenerator swe, Dict dict, Gridded mask, FillResult filled) {
boolean inBounds(int idx) { return idx >= 0 && idx < SwedishGenerator.SIZE; }
Placed extractPlacedFromSlot(Slot s, long lemma) {
var cells = s.walk().toArray();
return new Placed(
lemma,
s.key(),
cells
);
}
boolean inBounds(int idx) { return idx >= 0 && idx < SwedishGenerator.SIZE; }
Placed extractPlacedFromSlot(Slot s, long lemma) { return new Placed(lemma, s.key(), s.walk().toArray()); }
public ExportedPuzzle exportFormatFromFilled(int difficulty, Rewards rewards) {
var g = filled().grid();
var placed = new ArrayList<Placed>();

View File

@@ -1,6 +1,7 @@
package puzzle;
import lombok.Data;
import lombok.val;
import puzzle.SwedishGenerator.Rng;
import java.io.IOException;
@@ -346,7 +347,7 @@ public class Main {
static PuzzleResult attempt(Rng rng, Dict dict, Opts opts) {
try {
return _attempt(rng, dict, opts);
return _attempt(rng, dict, opts);
} catch (Exception e) {
e.printStackTrace();
System.err.println("Failed to operate" + e.getMessage());
@@ -356,8 +357,9 @@ public class Main {
static PuzzleResult _attempt(Rng rng, Dict dict, Opts opts) {
TOTAL_ATTEMPTS.incrementAndGet();
var swe = new SwedishGenerator(rng);
var mask = swe.generateMask(opts.pop, opts.gens, Math.max(opts.pop, (int) Math.floor(opts.pop * 1.5)));
var filled = swe.fillMask(mask, dict.index(), opts.fillTimeout);
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 filled = fillMask(rng, mask, dict.index());
TOTAL_NODES.addAndGet(filled.stats().nodes);
TOTAL_BACKTRACKS.addAndGet(filled.stats().backtracks);

View File

@@ -1,6 +1,9 @@
package puzzle;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Accessors;
import lombok.experimental.Delegate;
import lombok.val;
import precomp.Neighbors9x8;
import precomp.Neighbors9x8.rci;
@@ -59,9 +62,11 @@ public record SwedishGenerator(Rng rng) {
static final int MAX_WORD_LENGTH_PLUS_ONE = MAX_WORD_LENGTH + 1;
static final int MIN_LEN = Config.MIN_LEN;
static final int MAX_TRIES_PER_SLOT = Config.MAX_TRIES_PER_SLOT;
static final int STACK_SIZE = 64;
static final char C_DASH = '\0';
static final byte _1 = 49, _9 = 57, A = 65, Z = 90, DASH = (byte) C_DASH;
//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) { }
// 0b11
@@ -94,19 +99,22 @@ 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);
@RequiredArgsConstructor
@Getter
@Accessors(fluent = true)
public static final class FillStats {
public long nodes;
public long backtracks;
public double seconds;
public int lastMRV;
public double simplicity;
final public long nodes;
final public long backtracks;
final public double seconds;
final public int lastMRV;
public double simplicity;
}
public static record FillResult(boolean ok,
Gridded grid,
long[] clueMap,
FillStats stats) {
@Delegate FillStats stats) {
public void calcSimpel() {
if (ok) {
@@ -131,9 +139,9 @@ public record SwedishGenerator(Rng rng) {
}
}
static final record Context(int[] stack, long[] undo, long[] bitset) {
static final record Context(long[] bitset) {
public Context() { this(new int[SIZE], new long[128], new long[2500]); }
public Context() { this(new long[2500]); }
private static final ThreadLocal<Context> CTX = ThreadLocal.withInitial(Context::new);
public static Context get() { return CTX.get(); }
@@ -381,18 +389,18 @@ public record SwedishGenerator(Rng rng) {
}
}
if ((rayLo | rayHi) != 0) {
visitor.visit(key, rayLo, rayHi);
}
visitor.visit(key, rayLo, rayHi);
//if ((rayLo | rayHi) == 0L) throw new RuntimeException()
}
static ArrayList<Slot> extractSlots(Grid grid) {
var slots = new ArrayList<Slot>(32);
grid.forEachSlot((key, lo, hi) -> slots.add(Slot.from(key, lo, hi)));
static Slot[] extractSlots(Grid grid) {
var slots = new Slot[grid.clueCount()];
int[] N = new int[]{ 0 };
grid.forEachSlot((key, lo, hi) -> slots[N[0]++] = Slot.from(key, lo, hi));
return slots;
}
long maskFitness(Grid grid) {
long maskFitness(Grid grid, int[] stack) {
long cHLo = 0L, cHHi = 0L, cVLo = 0L, cVHi = 0L;
long lo_cl = grid.lo, hi_cl = grid.hi;
@@ -437,8 +445,6 @@ public record SwedishGenerator(Rng rng) {
}
if (!hasSlots) return 1_000_000_000L;
var ctx = Context.get();
var stack = ctx.stack;
long seenLo = 0L, seenHi = 0L;
// loop over beide helften
@@ -448,7 +454,7 @@ public record SwedishGenerator(Rng rng) {
// "unseen clues" in deze helft
for (long bits = clueMask & ~seenMask; bits != 0L; bits &= bits - 1) {
int clueIdx = base + Long.numberOfTrailingZeros(bits);
int clueIdx = base | Long.numberOfTrailingZeros(bits);
// start nieuwe component
int size = 0;
@@ -543,7 +549,7 @@ public record SwedishGenerator(Rng rng) {
for (var k = 0; k < 4; k++) {
ri = bytes[rng.randint(0, 624)];
if (!g.clueless(ri)) {
var d_idx = rng.randint2bitByte();
var d_idx = rng.randint2bitByte();
if (g.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotDir(ri, d_idx)])) g.setClue(ri, d_idx);
}
}
@@ -580,14 +586,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(Grid start, int limit) {
Grid hillclimb(int[] stack, Grid start, int limit) {
var best = start;
var bestF = maskFitness(best);
var bestF = maskFitness(best, stack);
var fails = 0;
while (fails < limit) {
var cand = mutate(best);
var f = maskFitness(cand);
var f = maskFitness(cand, stack);
if (f < bestF) {
best = cand;
bestF = f;
@@ -599,21 +605,21 @@ public record SwedishGenerator(Rng rng) {
return best;
}
public Grid generateMask(int popSize, int gens, int pairs) {
public Grid generateMask(int[] stack, 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);
if (fite == -1) this.fite = maskFitness(grid, stack);
return this.fite;
}
}
if (Main.VERBOSE) System.out.println("generateMask init pop: " + popSize);
var pop = new ArrayList<GridAndFit>();
for (var i = 0; i < popSize; i++) {
pop.add(new GridAndFit(hillclimb(randomMask(), 180)));
pop.add(new GridAndFit(hillclimb(stack, randomMask(), 180)));
}
for (var gen = 0; gen < gens; gen++) {
@@ -624,7 +630,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(child, 70)));
children.add(new GridAndFit(hillclimb(stack, child, 70)));
}
pop.addAll(children);
@@ -696,7 +702,7 @@ public record SwedishGenerator(Rng rng) {
}
return p;
}
static int slotScore(int[] count, Slot s) {
static int slotScore(byte[] count, Slot s) {
int cross = 0;
for (long b = s.lo; b != 0; b &= b - 1) cross += (count[Long.numberOfTrailingZeros(b)] - 1);
for (long b = s.hi; b != 0; b &= b - 1) cross += (count[64 | Long.numberOfTrailingZeros(b)] - 1);
@@ -761,9 +767,8 @@ public record SwedishGenerator(Rng rng) {
return true;
}
static CandidateInfo candidateInfoForPattern(Context ctx, long pattern, DictEntry entry, int lenb) {
static CandidateInfo candidateInfoForPattern(long[] res, long pattern, DictEntry entry, int lenb) {
int numLongs = entry.numlong;
long[] res = ctx.bitset;
boolean first = true;
for (long p = pattern; p != 0; ) {
@@ -799,15 +804,15 @@ public record SwedishGenerator(Rng rng) {
return new CandidateInfo(indices, count);
}
static int candidateCountForPattern(Context ctx, long pattern, DictEntry entry, int lenb) {
int numLongs = entry.numlong;
long[] res = ctx.bitset;
boolean first = true;
static int candidateCountForPattern(final long[] res, final long pattern, final DictEntry entry, final int lenb) {
val numLongs = entry.numlong;
val posBitsets = entry.posBitsets;
boolean first = true;
for (long p = pattern; p != 0; ) {
int combined = (int) (p & 0xFF);
if (combined != 0) {
long[] bs = entry.posBitsets[combined - 1];
long[] bs = posBitsets[combined - 1];
if (first) {
System.arraycopy(bs, 0, res, 0, numLongs);
first = false;
@@ -824,36 +829,40 @@ public record SwedishGenerator(Rng rng) {
for (int k = 0; k < numLongs; k++) count += Long.bitCount(res[k]);
return count;
}
//72 << 3;
static final int BIGG = (288 | 3) + 1;
public FillResult fillMask(Grid mask, DictEntry[] dictIndex,
int timeLimitMs) {
val multiThreaded = Thread.currentThread().getName().contains("pool");
val grid = mask.deepCopyGrid();
val used = new Bit1029();
long[] assigned = new long[BIGG];
val ctx = Context.get();
val count = new int[SIZE];
val slots = extractSlots(grid);
static void scoreSlots(int[] slotScores, Slot[] slots) {
val count = new byte[SIZE];
for (var s : slots) {
for (long b = s.lo; b != 0; b &= b - 1) count[Long.numberOfTrailingZeros(b)]++;
for (long b = s.hi; b != 0; b &= b - 1) count[64 | Long.numberOfTrailingZeros(b)]++;
}
for (int i = 0; i < slots.length; i++) slotScores[i] = slotScore(count, slots[i]);
val slotScores = new int[slots.size()];
for (int i = 0; i < slots.size(); i++) slotScores[i] = slotScore(count, slots.get(i));
}
public static FillResult fillMask(Rng rng, Grid mask, DictEntry[] dictIndex) {
val multiThreaded = Thread.currentThread().getName().contains("pool");
val NO_LOG = (!Main.VERBOSE || multiThreaded);
val grid = mask.deepCopyGrid();
val used = new Bit1029();
val assigned = new long[CLUE_INDEX_MAX_SIZE];
val bitset = new long[2500];
val undo = new long[64];
val t0 = System.currentTimeMillis();
val stats = new FillStats();
val TOTAL = slots.size();
val slots = extractSlots(grid);
val TOTAL = slots.length;
val slotScores = new int[TOTAL];
scoreSlots(slotScores, slots);
val t0 = System.currentTimeMillis();
class Solver {
long nodes;
long backtracks;
int lastMRV;
long lastLog = t0;
void renderProgress() {
if (!Main.VERBOSE || multiThreaded) return;
var now = System.currentTimeMillis();
if ((now - lastLog) < LOG_EVERY_MS) return;
lastLog = (now);
@@ -869,19 +878,19 @@ public record SwedishGenerator(Rng rng) {
var msg = String.format(
Locale.ROOT,
"%s %d/%d slots | nodes=%d | backtracks=%d | mrv=%d | %s",
bar, done, TOTAL, stats.nodes, stats.backtracks, stats.lastMRV, elapsed
bar, done, TOTAL, nodes, backtracks, lastMRV, elapsed
);
System.out.print("\r" + Strings.padRight(msg, 120));
System.out.flush();
}
Pick chooseMRV() {
Slot best = null;
for (int i = 0, count, count2 = -1, bestScore = -1, n = slots.size(); i < n; i++) {
var s = slots.get(i);
for (int i = 0, count, count2 = -1, bestScore = -1, n = TOTAL; i < n; i++) {
var s = slots[i];
if (assigned[s.key] != X) continue;
var pattern = patternForSlot(grid, s);
var index = dictIndex[s.length()];
count = pattern == X ? index.length : candidateCountForPattern(ctx, pattern, index, s.length());
count = pattern == X ? index.length : candidateCountForPattern(bitset, pattern, index, s.length());
if (count == 0) return PICK_NOT_DONE;
if (best == null
@@ -898,28 +907,27 @@ public record SwedishGenerator(Rng rng) {
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(ctx, pattern, index, best.length()), false);
return new Pick(best, candidateInfoForPattern(bitset, pattern, index, best.length()), false);
}
boolean backtrack(int depth) {
if (Thread.currentThread().isInterrupted()) return false;
stats.nodes++;
nodes++;
if (timeLimitMs > 0 && (System.currentTimeMillis() - t0) > timeLimitMs) return false;
if (20_000 > 0 && (System.currentTimeMillis() - t0) > 20_000) return false;
var pick = chooseMRV();
if (pick.done) return true;
if (pick.slot == null) {
stats.backtracks++;
backtracks++;
return false;
}
val info = pick.info;
stats.lastMRV = info.count;
renderProgress();
lastMRV = info.count;
if (!NO_LOG) renderProgress();
var s = pick.slot;
var k = s.key;
int patLen = s.length();
var entry = dictIndex[patLen];
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;
@@ -935,7 +943,7 @@ public record SwedishGenerator(Rng rng) {
var lemIdx = Lemma.unpackIndex(w);
if (used.get(lemIdx)) continue;
if (!placeWord(grid, s, w, ctx.undo, depth)) continue;
if (!placeWord(grid, s, w, undo, depth)) continue;
used.set(lemIdx);
assigned[k] = w;
@@ -944,9 +952,9 @@ public record SwedishGenerator(Rng rng) {
assigned[k] = X;
used.clear(lemIdx);
grid.undoPlace(ctx.undo[depth << 1], ctx.undo[(depth << 1) | 1]);
grid.undoPlace(undo[depth << 1], undo[(depth << 1) | 1]);
}
stats.backtracks++;
backtracks++;
return false;
}
@@ -960,7 +968,7 @@ public record SwedishGenerator(Rng rng) {
var lemIdx = Lemma.unpackIndex(w);
if (used.get(lemIdx)) continue;
if (!placeWord(grid, s, w, ctx.undo, depth)) continue;
if (!placeWord(grid, s, w, undo, depth)) continue;
used.set(lemIdx);
assigned[k] = w;
@@ -969,33 +977,31 @@ public record SwedishGenerator(Rng rng) {
assigned[k] = X;
used.clear(lemIdx);
grid.undoPlace(ctx.undo[depth << 1], ctx.undo[(depth << 1) | 1]);
grid.undoPlace(undo[depth << 1], undo[(depth << 1) | 1]);
}
stats.backtracks++;
backtracks++;
return false;
}
}
// initial render (same feel)
var solver = new Solver();
solver.renderProgress();
if (!NO_LOG) solver.renderProgress();
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));
if (!multiThreaded) {
System.out.print("\r" + Strings.padRight("", 120) + "\r");
System.out.flush();
}
stats.seconds = (System.currentTimeMillis() - t0) / 1000.0;
var res = new FillResult(ok, new Gridded(grid), assigned, 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",
res.wordCount(), TOTAL, stats.nodes, stats.backtracks, stats.lastMRV, stats.seconds
res.wordCount(), TOTAL, res.nodes(), res.backtracks(), res.lastMRV(), res.seconds()
)
);
}