introduce bitloops

This commit is contained in:
mike
2026-01-10 08:32:27 +01:00
parent 1521e461bc
commit a3df24e90f
3 changed files with 92 additions and 53 deletions

15
.run/Main.run.xml Normal file
View File

@@ -0,0 +1,15 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Main" type="Application" factoryName="Application" nameIsGenerated="true">
<option name="MAIN_CLASS_NAME" value="puzzle.Main" />
<module name="tools" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="puzzle.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View File

@@ -20,9 +20,7 @@ import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.IntConsumer;
import java.util.function.Supplier;
/**
* SwedishGenerator.java
@@ -67,6 +65,19 @@ public record SwedishGenerator(Rng rng) {
static final nbrs_8[] nbrs4 = Neighbors9x8.nbrs4;
static final rci[] IT = Neighbors9x8.IT;
static final long[] INBR8_PACKEDT = Neighbors9x8.NBR8_PACKED;
static final int[][] MUTATE_RI = new int[SIZE][625];
static {
for (int i = 0; i < SIZE; i++) {
int k = 0;
for (int dr1 = -2; dr1 <= 2; dr1++)
for (int dr2 = -2; dr2 <= 2; dr2++)
for (int dc1 = -2; dc1 <= 2; dc1++)
for (int dc2 = -2; dc2 <= 2; dc2++)
MUTATE_RI[i][k++] = Grid.offset(clamp(Grid.r(i) + dr1 + dr2, 0, R - 1),
clamp(Grid.c(i) + dc1 + dc2, 0, C - 1));
}
}
static final Pick PICK_DONE = new Pick(null, null, true);
static final Pick PICK_NOT_DONE = new Pick(null, null, false);
@@ -151,10 +162,6 @@ public record SwedishGenerator(Rng rng) {
Grid deepCopyGrid() { return new Grid(g.clone(), bo.clone()); }
public byte byteAt(int pos) { return g[pos]; }
void setByteAt(int idx, byte ch) { g[idx] = ch; }
void setAt(int idx, byte ch) {
if (isDigit(ch)) setClue(idx, ch);
else g[idx] = ch;
}
void setClue(int idx, byte ch) {
g[idx] = ch;
if (idx < 64) bo[0] |= (1L << idx);
@@ -378,19 +385,18 @@ public record SwedishGenerator(Rng rng) {
grid.forEachSlot((key, packedPos, len) -> slots.add(Slot.from(key, packedPos, len)));
return slots;
}
boolean hasRoomForClue(Grid grid, long packed) {
static boolean hasRoomForClue(Grid grid, long packed) {
for (int n = (int) (packed >>> 56), k = 0; k < n && k < MAX_WORD_LENGTH; ) {
if (!grid.isLetterAt((int) ((packed >>> (k * 7)) & 0x7F))) break;
if (grid.isClue((int) ((packed >>> (k * 7)) & 0x7F))) break;
if (++k >= MIN_LEN) return true;
}
return false;
}
long maskFitness(Grid grid) {
final long[] penalty = { 0 };
var clueCount = grid.clueCount();
penalty[0] += ((long) Math.abs(clueCount - TARGET_CLUES)) << 3;
var clueCount = grid.clueCount();
long penalty = ((long) Math.abs(clueCount - TARGET_CLUES)) << 3;
var ctx = CTX.get();
var covH = ctx.covH;
@@ -415,7 +421,7 @@ public record SwedishGenerator(Rng rng) {
}
if (k == 0) continue;
hasSlots = true;
if (k < MIN_LEN) penalty[0] += 8000;
if (k < MIN_LEN) penalty += 8000;
}
while (hi_cl != 0L) {
int clueIdx = 64 + Long.numberOfTrailingZeros(hi_cl);
@@ -432,7 +438,7 @@ public record SwedishGenerator(Rng rng) {
}
if (k == 0) continue;
hasSlots = true;
if (k < MIN_LEN) penalty[0] += 8000;
if (k < MIN_LEN) penalty += 8000;
}
if (!hasSlots) return 1_000_000_000L;
@@ -442,27 +448,9 @@ public record SwedishGenerator(Rng rng) {
seen.clear();
var stack = ctx.stack;
grid.forEachSetBit71(clueIdx -> {
if (seen.get(clueIdx)) return;
var sp = 0;
stack[sp++] = clueIdx;
seen.set(clueIdx);
var size = 0;
while (sp > 0) {
var p = stack[--sp];
size++;
long packed = Neighbors9x8.NBR8_PACKED[p];
int n = (int) (packed >>> 56);
for (int k = 0; k < n; k++) {
int nidx = (int) ((packed >>> (k * 7)) & 0x7F);
if (seen.get(nidx) || grid.notClue(nidx)) continue;
seen.set(nidx);
stack[sp++] = nidx;
}
}
if (size >= 2) penalty[0] += (size - 1L) * 120L;
});
for (var lo = grid.bo[0]; lo != 0; lo &= lo - 1) penalty += clueStackPenalty(seen, stack, grid, Long.numberOfTrailingZeros(lo));
for (var hi = grid.bo[1]; hi != 0; hi &= hi - 1) penalty += clueStackPenalty(seen, stack, grid, 64 + Long.numberOfTrailingZeros(hi));
// dead-end-ish letter cell (3+ walls)
int walls, wc, wr;
/* for (var rci : IT) {
@@ -478,21 +466,40 @@ public record SwedishGenerator(Rng rng) {
int h, v;
for (var rci : IT) {
if (grid.isClue(rci.i())) continue;
if ((4 - rci.nbrCount()) + Long.bitCount(rci.n1() & grid.bo[0]) + Long.bitCount(rci.n2() & grid.bo[1]) >= 3) penalty[0] += 400;
if ((4 - rci.nbrCount()) + Long.bitCount(rci.n1() & grid.bo[0]) + Long.bitCount(rci.n2() & grid.bo[1]) >= 3) penalty += 400;
h = covH[rci.i()];
v = covV[rci.i()];
if (h == 0 && v == 0) penalty[0] += 1500;
else if (h > 0 && v > 0) { /* ok */ } else if (h + v == 1) penalty[0] += 200;
else penalty[0] += 600;
if (h == 0 && v == 0) penalty += 1500;
else if (h > 0 && v > 0) { /* ok */ } else if (h + v == 1) penalty += 200;
else penalty += 600;
}
return penalty[0];
return penalty;
}
static long clueStackPenalty(Bit seen, int[] stack, Grid grid, int clueIdx) {
if (seen.get(clueIdx)) return 0;
var sp = 0;
stack[sp++] = clueIdx;
seen.set(clueIdx);
var size = 0;
while (sp > 0) {
var p = stack[--sp];
size++;
long packed = Neighbors9x8.NBR8_PACKED[p];
int n = (int) (packed >>> 56);
for (int k = 0; k < n; k++) {
int nidx = (int) ((packed >>> (k * 7)) & 0x7F);
if (seen.get(nidx) || grid.notClue(nidx)) continue;
seen.set(nidx);
stack[sp++] = nidx;
}
}
return (size >= 2) ? (size - 1L) * 120L : 0;
}
Grid randomMask() {
var g = Grid.createEmpty();
for (int placed = 0, guard = 0, idx; placed < TARGET_CLUES && guard < 4000; guard++) {
idx = Grid.offset(rng.randint(0, R - 1),
rng.randint(0, C - 1));
idx = rng.randint(0, SIZE - 1);
if (g.isClue(idx)) continue;
var d = OFFSETS[rng.randbyte(1, 4)];
@@ -504,15 +511,12 @@ public record SwedishGenerator(Rng rng) {
return g;
}
Grid mutate(Grid grid) {
var g = grid.deepCopyGrid();
var cx = rng.randint(0, R - 1);
var cy = rng.randint(0, C - 1);
var g = grid.deepCopyGrid();
var centerIdx = rng.randint(0, SIZE - 1);
var steps = 4;
for (var k = 0; k < steps; k++) {
var ri = Grid.offset(
clamp(cx + (rng.randint(-2, 2) + rng.randint(-2, 2)), 0, R - 1),
clamp(cy + (rng.randint(-2, 2) + rng.randint(-2, 2)), 0, C - 1));
var ri = MUTATE_RI[centerIdx][rng.randint(0, 624)];
if (!g.clueless(ri)) {
var d = OFFSETS[rng.randint(1, 4)];
@@ -522,15 +526,35 @@ public record SwedishGenerator(Rng rng) {
return g;
}
Grid crossover(Grid a, Grid b) {
var out = Grid.createEmpty();
var out = a.deepCopyGrid();
var theta = rng.nextFloat() * Math.PI;
var nc = Math.cos(theta);
var nr = Math.sin(theta);
for (var rci : IT) out.setAt(rci.i(), ((rci.r() - CROSS_C) * nc + (rci.c() - CROSS_R) * nr >= 0) ? a.byteAt(rci.i()) : b.byteAt(rci.i()));
for (var rci : IT) if (out.isClue(rci.i()) && !hasRoomForClue(out, OFFSETS[out.digitAt(rci.i())].path()[rci.i()])) out.clearClue(rci.i());
long bo0 = out.bo[0], bo1 = out.bo[1];
for (var rci : IT) {
int i = rci.i();
if ((rci.r() - CROSS_C) * nc + (rci.c() - CROSS_R) * nr < 0) {
byte ch = b.g[i];
if (out.g[i] != ch) {
out.g[i] = ch;
if (Grid.isDigit(ch)) {
if (i < 64) bo0 |= (1L << i);
else bo1 |= (1L << (i & 63));
} else {
if (i < 64) bo0 &= ~(1L << i);
else bo1 &= ~(1L << (i & 63));
}
}
}
}
out.bo[0] = bo0;
out.bo[1] = bo1;
for (var lo = out.bo[0]; lo != 0L; lo &= lo - 1L) clearClues(out, Long.numberOfTrailingZeros(lo));
for (var hi = out.bo[1]; hi != 0L; hi &= hi - 1L) clearClues(out, 64 + Long.numberOfTrailingZeros(hi));
return out;
}
public static void clearClues(Grid out, int idx) { if (!hasRoomForClue(out, OFFSETS[out.digitAt(idx)].path()[idx])) out.clearClue(idx); }
Grid hillclimb(Grid start, int limit) {
var best = start;

View File

@@ -167,8 +167,8 @@ 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(767.8636363636364, res.filled().stats().simplicity, 1e-9, "Simplicity value changed");
Assertions.assertArrayEquals(new byte[]{ 'T', 'R', 'U', 'I' }, res.filled().clueMap().get(515).word());
Assertions.assertEquals(747.5454545454545, res.filled().stats().simplicity, 1e-9, "Simplicity value changed");
Assertions.assertArrayEquals(new byte[]{ 'I', 'E', 'M', 'A', 'N', 'D', 'S' }, res.filled().clueMap().get(515).word());
}
@Test
public void testIsLetterA() {