introduce bitloops
This commit is contained in:
@@ -8,7 +8,7 @@ OPENAI_API_KEY="local"
|
||||
# Constants for testing
|
||||
CLUE_SIZE=4
|
||||
MIN_LEN=2
|
||||
MAX_TRIES_PER_SLOT=2000
|
||||
MAX_TRIES_PER_SLOT=1000
|
||||
MAX_LEN=8
|
||||
PUZZLE_ROWS=3
|
||||
PUZZLE_COLS=3
|
||||
|
||||
@@ -44,12 +44,12 @@ public class Main {
|
||||
@NoArgsConstructor
|
||||
public static class Opts {
|
||||
|
||||
static int SSIZE = 20;
|
||||
static int SSIZE = 25;
|
||||
public int seed = (int) (System.nanoTime() ^ System.currentTimeMillis());
|
||||
public int clueSize = SSIZE;
|
||||
public int pop = SSIZE * 2;
|
||||
public int offspring = SSIZE * 3;
|
||||
public int gens = 600;
|
||||
public int pop = SSIZE*2;
|
||||
public int offspring = SSIZE*3;
|
||||
public int gens =1200;
|
||||
public String wordsPath = "nl_score_hints_v3.csv";
|
||||
public double minSimplicity = 0; // 0 means no limit
|
||||
public int threads = Math.max(1, Runtime.getRuntime().availableProcessors());
|
||||
@@ -368,11 +368,12 @@ public class Main {
|
||||
try {
|
||||
return _attempt(rng, dict, opts);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.err.println("Failed to operate" + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
static Clues generateClues() {
|
||||
static Clued generateClues() {
|
||||
String simple = "000 3000\n" +
|
||||
" 3 \n" +
|
||||
" 31 \n" +
|
||||
|
||||
@@ -2,10 +2,13 @@ package puzzle;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.val;
|
||||
import puzzle.Export.Clued;
|
||||
import puzzle.Export.Gridded;
|
||||
|
||||
import java.sql.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import static java.lang.Long.*;
|
||||
@@ -48,8 +51,8 @@ public record Masker(Rng rng, int[] stack, Clues cache) {
|
||||
long stop = 1L << msb;
|
||||
rayLo &= -(stop << 1);
|
||||
}
|
||||
|
||||
visitor.visit(key, rayLo, rayHi);
|
||||
// if (Long.bitCount(rayLo) + Long.bitCount(rayHi) > 1)
|
||||
visitor.visit(key, rayLo, rayHi);
|
||||
}
|
||||
private static void processSlot(Clues c, SlotVisitor visitor, int key) {
|
||||
long rayLo = PATH_LO[key];
|
||||
@@ -66,14 +69,13 @@ public record Masker(Rng rng, int[] stack, Clues cache) {
|
||||
// keep all lo (lo indices are < any hi index), but cut hi below stop
|
||||
rayHi &= (stop - 1);
|
||||
}
|
||||
|
||||
visitor.visit(key, rayLo, rayHi);
|
||||
// if (Long.bitCount(rayLo) + Long.bitCount(rayHi) > 1)
|
||||
visitor.visit(key, rayLo, rayHi);
|
||||
}
|
||||
public static Slot[] extractSlots(Clues grid, DictEntry[] index) {
|
||||
var slots = new Slot[grid.clueCount()];
|
||||
int[] N = new int[]{ 0 };
|
||||
grid.forEachSlot((key, lo, hi) -> slots[N[0]++] = Slot.from(key, lo, hi, index[Slot.length(lo, hi)]));
|
||||
return slots;
|
||||
var slots = new ArrayList<Slot>(grid.clueCount());
|
||||
grid.forEachSlot((key, lo, hi) -> slots.add(Slot.from(key, lo, hi, Objects.requireNonNull(index[Slot.length(lo, hi)]))));
|
||||
return slots.toArray(Slot[]::new);
|
||||
}
|
||||
public static Slotinfo[] slots(Clues mask, DictEntry[] index) {
|
||||
var slots = Masker.extractSlots(mask, index);
|
||||
@@ -102,6 +104,7 @@ public record Masker(Rng rng, int[] stack, Clues cache) {
|
||||
public long maskFitness(final Clues grid, int clueSize) {
|
||||
|
||||
long cHLo = 0L, cHHi = 0L, cVLo = 0L, cVHi = 0L;
|
||||
long cHLo2 = 0L, cHHi2 = 0L, cVLo2 = 0L, cVHi2 = 0L;
|
||||
long lo_cl = grid.lo, hi_cl = grid.hi;
|
||||
long penalty = (((long) Math.abs(grid.clueCount() - clueSize)) * 16000L);
|
||||
boolean hasSlots = false;
|
||||
@@ -130,9 +133,11 @@ public record Masker(Rng rng, int[] stack, Clues cache) {
|
||||
if ((rLo | rHi) != X) {
|
||||
hasSlots = true;
|
||||
if (Slot.horiz(key)) {
|
||||
cHLo2 |= (cHLo & rLo); cHHi2 |= (cHHi & rHi);
|
||||
cHLo |= rLo;
|
||||
cHHi |= rHi;
|
||||
} else {
|
||||
cVLo2 |= (cVLo & rLo); cVHi2 |= (cVHi & rHi);
|
||||
cVLo |= rLo;
|
||||
cVHi |= rHi;
|
||||
}
|
||||
@@ -163,9 +168,11 @@ public record Masker(Rng rng, int[] stack, Clues cache) {
|
||||
if ((rLo | rHi) != X) {
|
||||
hasSlots = true;
|
||||
if (Slot.horiz(key)) {
|
||||
cHLo2 |= (cHLo & rLo); cHHi2 |= (cHHi & rHi);
|
||||
cHLo |= rLo;
|
||||
cHHi |= rHi;
|
||||
} else {
|
||||
cVLo2 |= (cVLo & rLo); cVHi2 |= (cVHi & rHi);
|
||||
cVLo |= rLo;
|
||||
cVHi |= rHi;
|
||||
}
|
||||
@@ -241,8 +248,9 @@ public record Masker(Rng rng, int[] stack, Clues cache) {
|
||||
boolean h = (cHLo & (1L << clueIdx)) != X;
|
||||
boolean v = (cVLo & (1L << clueIdx)) != X;
|
||||
if (!h && !v) penalty += 1500;
|
||||
else if (h && v) { /* ok */ } else if (h | v) penalty += 200;
|
||||
else penalty += 600;
|
||||
else if (h && v) { /* ok */ }
|
||||
else if (((h ? cHLo2 : cVLo2) & (1L << clueIdx)) != X) penalty += 600;
|
||||
else penalty += 200;
|
||||
}
|
||||
for (long bits = ~hi_cl & MASK_HI; bits != X; bits &= bits - 1) {
|
||||
int clueIdx = numberOfTrailingZeros(bits);
|
||||
@@ -251,8 +259,9 @@ public record Masker(Rng rng, int[] stack, Clues cache) {
|
||||
boolean h = (cHHi & (1L << clueIdx)) != X;
|
||||
boolean v = (cVHi & (1L << clueIdx)) != X;
|
||||
if (!h && !v) penalty += 1500;
|
||||
else if (h && v) { /* ok */ } else if (h | v) penalty += 200;
|
||||
else penalty += 600;
|
||||
else if (h && v) { /* ok */ }
|
||||
else if (((h ? cHHi2 : cVHi2) & (1L << clueIdx)) != X) penalty += 600;
|
||||
else penalty += 200;
|
||||
}
|
||||
|
||||
return penalty;
|
||||
@@ -329,7 +338,7 @@ public record Masker(Rng rng, int[] stack, Clues cache) {
|
||||
for (var h = c.hi & ~c.rhi & ~c.vhi; h != X; h &= h - 1) clearCluesHi(c, numberOfTrailingZeros(h), 0);
|
||||
for (var h = c.hi & ~c.rhi & c.vhi; h != X; h &= h - 1) clearCluesHi(c, numberOfTrailingZeros(h), 1);
|
||||
for (var h = c.hi & c.rhi & ~c.vhi; h != X; h &= h - 1) clearCluesHi(c, (numberOfTrailingZeros(h)), 2);
|
||||
for (var h = c.hi & c.rhi & ~c.vhi; h != X; h &= h - 1) clearCluesHi(c, (numberOfTrailingZeros(h)), 3);
|
||||
for (var h = c.hi & c.rhi & c.vhi; h != X; h &= h - 1) clearCluesHi(c, (numberOfTrailingZeros(h)), 3);
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -421,7 +430,7 @@ public record Masker(Rng rng, int[] stack, Clues cache) {
|
||||
long lo, hi, vlo, vhi, rlo, rhi;
|
||||
|
||||
public static Clues createEmpty() { return new Clues(0, 0, 0, 0, 0, 0); }
|
||||
public static Clues parse(String s) {
|
||||
public static Clued parse(String s) {
|
||||
var c = createEmpty();
|
||||
var lines = s.split("\n");
|
||||
for (int r = 0; r < Math.min(lines.length, R); r++) {
|
||||
@@ -436,7 +445,7 @@ public record Masker(Rng rng, int[] stack, Clues cache) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return c;
|
||||
return new Clued(c);
|
||||
}
|
||||
public boolean cluelessLo(int idx) {
|
||||
if (!isClueLo(idx)) return false;
|
||||
|
||||
@@ -45,7 +45,6 @@ public class Meta {
|
||||
val parts = string.split("\t", 3);
|
||||
return new ShardLem(Lemma.from(parts[0]), Integer.parseInt(parts[1]), GSON.fromJson(parts[2], String[].class));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return new ShardLem(Lemma.from("XXX"), -1, new String[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,8 +48,8 @@ public record SwedishGenerator() {
|
||||
public static final long MASK_HI = (SIZE <= 64) ? 0L : (SIZE >= 128 ? -1L : (1L << (SIZE - 64)) - 1);
|
||||
public static final int MAX_WORD_LENGTH = C <= R ? C : R;
|
||||
public static final int MAX_WORD_LENGTH_PLUS_ONE = MAX_WORD_LENGTH + 1;
|
||||
public static final int MIN_LEN = Config.MIN_LEN;
|
||||
public static final int MAX_TRIES_PER_SLOT = Config.MAX_TRIES_PER_SLOT;
|
||||
public static final int MIN_LEN = 3;//Config.MIN_LEN;
|
||||
public static final int MAX_TRIES_PER_SLOT = 700;//Config.MAX_TRIES_PER_SLOT;
|
||||
public static final int STACK_SIZE = 64;
|
||||
public static final char C_DASH = '\0';
|
||||
public static final byte DASH = (byte) C_DASH;
|
||||
@@ -194,6 +194,14 @@ public record SwedishGenerator() {
|
||||
return k;
|
||||
}
|
||||
public static boolean increasing(int dir) { return (dir & 2) == 0; }
|
||||
public static Grid grid(Slotinfo[] slots) {
|
||||
long lo = X, hi = X;
|
||||
for (var slot : slots) {
|
||||
lo |= slot.lo;
|
||||
hi |= slot.hi;
|
||||
}
|
||||
return new Grid(new byte[SIZE], ~lo, ~hi /*& 0xFF*/);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isLo(int n) { return (n & 64) == 0; }
|
||||
@@ -318,8 +326,7 @@ public record SwedishGenerator() {
|
||||
int idx;
|
||||
for (long b = lo & glo; b != X; b &= b - 1) if (g[idx = numberOfTrailingZeros(b)] != Lemma.byteAt(w, bitCount(lo & ((1L << idx) - 1)))) return false;
|
||||
int bcLo = bitCount(lo);
|
||||
for (long b = hi & ghi; b != X; b &= b - 1)
|
||||
if (g[64 | (idx = numberOfTrailingZeros(b))] != Lemma.byteAt(w, bcLo + bitCount(hi & ((1L << idx) - 1)))) return false;
|
||||
for (long b = hi & ghi; b != X; b &= b - 1) if (g[64 | (idx = numberOfTrailingZeros(b))] != Lemma.byteAt(w, bcLo + bitCount(hi & ((1L << idx) - 1)))) return false;
|
||||
|
||||
long maskLo = lo & ~glo, maskHi = hi & ~ghi;
|
||||
if ((maskLo | maskHi) != X) {
|
||||
@@ -393,9 +400,9 @@ public record SwedishGenerator() {
|
||||
for (var t = 0; t < tries; t++) {
|
||||
//var r = rng.nextFloat();
|
||||
//var idxInArray = (int) (r * r * r * (L - 1));
|
||||
int idxInArray = rng.biasedIndexPow3(L - 1);
|
||||
var w = entry.words[idxs[idxInArray/*(int) (r * r * r * (L - 1))*/]];
|
||||
var lemIdx = Lemma.unpackIndex(w);
|
||||
int idxInArray = rng.biasedIndexPow3(L - 1);
|
||||
var w = entry.words[idxs[idxInArray/*(int) (r * r * r * (L - 1))*/]];
|
||||
var lemIdx = Lemma.unpackIndex(w);
|
||||
if (Bit1029.get(used, lemIdx)) continue;
|
||||
low = glo;
|
||||
top = ghi;
|
||||
|
||||
@@ -5,7 +5,6 @@ import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import puzzle.Export.ClueAt;
|
||||
import puzzle.Export.Clued;
|
||||
import puzzle.Export.Dicts;
|
||||
import puzzle.Export.Gridded;
|
||||
import puzzle.Export.LetterVisit.LetterAt;
|
||||
import puzzle.Export.PuzzleResult;
|
||||
@@ -13,26 +12,39 @@ import puzzle.Export.Rewards;
|
||||
import puzzle.Main.Opts;
|
||||
import puzzle.Masker.Clues;
|
||||
import puzzle.SwedishGenerator.Rng;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static puzzle.ExportFormatTest.Clue.*;
|
||||
import static puzzle.SwedishGenerator.*;
|
||||
import static puzzle.SwedishGeneratorTest.*;
|
||||
import static puzzle.ExportFormatTest.Clue.DOWN;
|
||||
import static puzzle.ExportFormatTest.Clue.LEFT;
|
||||
import static puzzle.ExportFormatTest.Clue.RIGHT;
|
||||
import static puzzle.ExportFormatTest.Clue.UP;
|
||||
import static puzzle.SwedishGenerator.Dict;
|
||||
import static puzzle.SwedishGenerator.Lemma;
|
||||
import static puzzle.SwedishGenerator.STACK_SIZE;
|
||||
import static puzzle.SwedishGenerator.Slotinfo;
|
||||
import static puzzle.SwedishGenerator.fillMask;
|
||||
import static puzzle.SwedishGeneratorTest.AB;
|
||||
import static puzzle.SwedishGeneratorTest.AZ;
|
||||
import static puzzle.SwedishGeneratorTest.CLUE_LEFT;
|
||||
import static puzzle.SwedishGeneratorTest.CLUE_RIGHT;
|
||||
import static puzzle.SwedishGeneratorTest.CLUE_UP;
|
||||
import static puzzle.SwedishGeneratorTest.Idx.IDX_0_0;
|
||||
import static puzzle.SwedishGeneratorTest.Idx.IDX_0_1;
|
||||
import static puzzle.SwedishGeneratorTest.Idx.IDX_1_0;
|
||||
import static puzzle.SwedishGeneratorTest.Idx.IDX_1_1;
|
||||
import static puzzle.SwedishGeneratorTest.Idx.IDX_2_1;
|
||||
import static puzzle.SwedishGeneratorTest.LETTER_A;
|
||||
import static puzzle.SwedishGeneratorTest.LETTER_Z;
|
||||
import static puzzle.SwedishGeneratorTest.OFF_0_0;
|
||||
import static puzzle.SwedishGeneratorTest.OFF_0_1;
|
||||
import static puzzle.SwedishGeneratorTest.OFF_0_2;
|
||||
import static puzzle.SwedishGeneratorTest.OFF_1_1;
|
||||
import static puzzle.SwedishGeneratorTest.OFF_1_2;
|
||||
import static puzzle.SwedishGeneratorTest.OFF_2_1;
|
||||
import static puzzle.SwedishGeneratorTest.OFF_2_3;
|
||||
|
||||
public class MainTest {
|
||||
|
||||
@@ -47,7 +59,7 @@ public class MainTest {
|
||||
this.tries = 1;
|
||||
this.verbose = false;
|
||||
}};
|
||||
static final Dict dict = DictData.DICT;//loadDict(opts.wordsPath);
|
||||
final Dict dict = DictData.DICT;//loadDict(opts.wordsPath);
|
||||
@Test
|
||||
void testExtractSlots() {
|
||||
|
||||
@@ -162,18 +174,12 @@ public class MainTest {
|
||||
var masker = new Masker(new Rng(12348), new int[STACK_SIZE], Masker.Clues.createEmpty());
|
||||
var mask = masker.generateMask(opts.clueSize, opts.pop, opts.gens, opts.offspring);
|
||||
val clued = new Clued(mask);
|
||||
val test = clued.gridToString();
|
||||
val RESULT = "1 \n" +
|
||||
" \n" +
|
||||
" 3\n" +
|
||||
" \n" +
|
||||
" \n" +
|
||||
"1 \n" +
|
||||
" \n" +
|
||||
" 3";
|
||||
|
||||
Assertions.assertEquals(4, clued.clueCount(), "Found seed changed");
|
||||
Assertions.assertEquals(RESULT, test, "Found seed changed");
|
||||
val map = clued.stream().collect(Collectors.toMap(ClueAt::index, ClueAt::clue));
|
||||
Assertions.assertEquals(4, map.size());
|
||||
Assertions.assertEquals(LEFT.dir, map.get(64));
|
||||
Assertions.assertEquals(RIGHT.dir, map.get(2));
|
||||
Assertions.assertEquals(LEFT.dir, map.get(67));
|
||||
Assertions.assertEquals(RIGHT.dir, map.get(7));
|
||||
}
|
||||
@Test
|
||||
void testFiller2() {
|
||||
@@ -188,10 +194,12 @@ public class MainTest {
|
||||
"3 \n" +
|
||||
"222 3");
|
||||
Assertions.assertEquals(20, mask.clueCount());
|
||||
var slots = Masker.extractSlots(mask, dict.index());
|
||||
val map = mask.stream().collect(Collectors.toMap(ClueAt::index, ClueAt::clue));
|
||||
Assertions.assertEquals(20, map.size());
|
||||
var slots = Masker.extractSlots(mask.c(), dict.index());
|
||||
val slotInfo = Masker.scoreSlots(slots);
|
||||
var grid = mask.toGrid();
|
||||
// var filled = fillMask(rng, slotInfo, grid, false);
|
||||
// var filled = fillMask(rng, slotInfo, grid, false);
|
||||
// val res = new PuzzleResult(new Clued(mask), new Gridded(grid), slotInfo, filled).exportFormatFromFilled(0, new Rewards(0, 0, 0));
|
||||
}
|
||||
@Test
|
||||
@@ -206,7 +214,7 @@ public class MainTest {
|
||||
" 1 \n" +
|
||||
" 1 2\n" +
|
||||
"21 22 3");
|
||||
var slots = Masker.extractSlots(mask, dict.index());
|
||||
var slots = Masker.extractSlots(mask.c(), dict.index());
|
||||
val slotInfo = Masker.scoreSlots(slots);
|
||||
var grid = mask.toGrid();
|
||||
var filled = fillMask(rng, slotInfo, grid, false);
|
||||
@@ -216,8 +224,8 @@ public class MainTest {
|
||||
Assertions.assertEquals(-1L, grid.lo);
|
||||
Assertions.assertEquals(255L, grid.hi);
|
||||
var g = new Gridded(grid);
|
||||
g.gridToString(mask);
|
||||
var aa = new PuzzleResult(new Clued(mask), g, slotInfo, filled).exportFormatFromFilled(1, new Rewards(1, 1, 1));
|
||||
g.gridToString(mask.c());
|
||||
var aa = new PuzzleResult(mask, g, slotInfo, filled).exportFormatFromFilled(1, new Rewards(1, 1, 1));
|
||||
System.out.println(String.join("\n", aa.grid()));
|
||||
|
||||
}
|
||||
|
||||
@@ -3,64 +3,197 @@ package puzzle;
|
||||
import lombok.val;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import puzzle.SwedishGenerator.DictEntry;
|
||||
import puzzle.SwedishGenerator.Rng;
|
||||
import puzzle.SwedishGenerator.Slotinfo;
|
||||
import puzzle.SwedishGenerator.Grid;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static puzzle.SwedishGenerator.fillMask;
|
||||
|
||||
public class PerformanceTest {
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
void testPerformance() {
|
||||
val rng = new Rng(42);
|
||||
val dict = DictData.DICT;
|
||||
|
||||
// 1. Stress test Clue Generation (Mask Generation)
|
||||
System.out.println("[DEBUG_LOG] --- Mask Generation Performance ---");
|
||||
int[] clueSizes = {20, 25, 30};
|
||||
for (int size : clueSizes) {
|
||||
long t0 = System.currentTimeMillis();
|
||||
val masker = new Masker(rng, new int[SwedishGenerator.STACK_SIZE], Masker.Clues.createEmpty());
|
||||
// Increased population and generations for stress
|
||||
val mask = masker.generateMask(size, 200, 100, 50);
|
||||
long t1 = System.currentTimeMillis();
|
||||
double duration = (t1 - t0) / 1000.0;
|
||||
System.out.printf(Locale.ROOT, "[DEBUG_LOG] Size %d (pop=200, gen=100): %.3fs%n", size, duration);
|
||||
// Basic sanity check: should not take forever
|
||||
assertTrue(duration < 10.0, "Mask generation took too long for size " + size);
|
||||
}
|
||||
|
||||
// 2. Stress test Word Filler
|
||||
System.out.println("[DEBUG_LOG] \n--- Word Filler Performance ---");
|
||||
for (int size : clueSizes) {
|
||||
val masker = new Masker(rng, new int[SwedishGenerator.STACK_SIZE], Masker.Clues.createEmpty());
|
||||
val mask = masker.generateMask(size, 100, 50, 20);
|
||||
|
||||
val slots = Masker.extractSlots(mask, dict.index());
|
||||
val slotInfo = Masker.scoreSlots(slots);
|
||||
val grid = mask.toGrid();
|
||||
|
||||
long t0 = System.currentTimeMillis();
|
||||
// Try to fill multiple times to get a better average
|
||||
int iterations = 3;
|
||||
long totalNodes = 0;
|
||||
long totalBacktracks = 0;
|
||||
int successCount = 0;
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
val result = fillMask(rng, slotInfo, grid.copy(), false);
|
||||
if (result.ok()) successCount++;
|
||||
totalNodes += result.nodes();
|
||||
totalBacktracks += result.backtracks();
|
||||
|
||||
final DictEntry[] EN = DictData.DICT.index();
|
||||
@Test
|
||||
void testPerformance() {
|
||||
val rng = new Rng(42);
|
||||
|
||||
// 1. Stress test Clue Generation (Mask Generation)
|
||||
System.out.println("[DEBUG_LOG] --- Mask Generation Performance ---");
|
||||
int[] clueSizes = { 20, 25, 30 };
|
||||
for (int size : clueSizes) {
|
||||
long t0 = System.currentTimeMillis();
|
||||
val masker = new Masker(rng, new int[SwedishGenerator.STACK_SIZE], Masker.Clues.createEmpty());
|
||||
// Increased population and generations for stress
|
||||
val mask = masker.generateMask(size, 200, 100, 50);
|
||||
long t1 = System.currentTimeMillis();
|
||||
double duration = (t1 - t0) / 1000.0;
|
||||
System.out.printf(Locale.ROOT, "[DEBUG_LOG] Size %d (pop=200, gen=100): %.3fs%n", size, duration);
|
||||
// Basic sanity check: should not take forever
|
||||
assertTrue(duration < 10.0, "Mask generation took too long for size " + size);
|
||||
}
|
||||
|
||||
// 2. Stress test Word Filler
|
||||
System.out.println("[DEBUG_LOG] \n--- Word Filler Performance ---");
|
||||
for (int size : clueSizes) {
|
||||
val masker = new Masker(rng, new int[SwedishGenerator.STACK_SIZE], Masker.Clues.createEmpty());
|
||||
val mask = masker.generateMask(size, 100, 50, 20);
|
||||
|
||||
val slotInfo = Masker.slots(mask, EN);
|
||||
val grid = mask.toGrid();
|
||||
|
||||
long t0 = System.currentTimeMillis();
|
||||
// Try to fill multiple times to get a better average
|
||||
int iterations = 1;
|
||||
long totalNodes = 0;
|
||||
long totalBacktracks = 0;
|
||||
int successCount = 0;
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
val result = fillMask(rng, slotInfo, grid.copy(), false);
|
||||
if (result.ok()) successCount++;
|
||||
totalNodes += result.nodes();
|
||||
totalBacktracks += result.backtracks();
|
||||
}
|
||||
long t1 = System.currentTimeMillis();
|
||||
double totalDuration = (t1 - t0) / 1000.0;
|
||||
|
||||
System.out.printf(Locale.ROOT, "[DEBUG_LOG] Size %d: %d/%d SUCCESS | avg nodes=%d | avg backtracks=%d | total time=%.3fs%n",
|
||||
size, successCount, iterations, totalNodes / iterations, totalBacktracks / iterations, totalDuration);
|
||||
}
|
||||
}
|
||||
void main() {
|
||||
testIncrementalComplexity();
|
||||
}
|
||||
@Test
|
||||
void testIncrementalComplexity() {
|
||||
|
||||
// Use the complex mask from Main.java
|
||||
String maskStr = "1 0000\n" +
|
||||
"1 \n" +
|
||||
"00 01 \n" +
|
||||
" 1 \n" +
|
||||
" 1 \n" +
|
||||
" 2 1 \n" +
|
||||
" 1 \n" +
|
||||
"221 22\n";
|
||||
val mask = Masker.Clues.parse(maskStr);
|
||||
val allSlots = Masker.slots(mask.c(), EN);
|
||||
//mask.toGrid()
|
||||
System.out.println("[DEBUG_LOG] \n--- Incremental Complexity Test ---");
|
||||
System.out.println("[DEBUG_LOG] Full Slot Layout:");
|
||||
visualizeSlots(allSlots);
|
||||
|
||||
for (int i = 10; i <= allSlots.length; i++) {
|
||||
val subset = Arrays.copyOf(allSlots, i);
|
||||
// Arrays.sort(subset, Comparator.comparingInt(Slotinfo::score));
|
||||
System.out.printf("[DEBUG_LOG] Testing with first %d slots%n of %s", i, allSlots.length);
|
||||
var grid = Slotinfo.grid(subset);
|
||||
visualizeSlots(subset);
|
||||
measureFill(new Rng(123 + i), subset, grid, "Subset size " + i);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSingleSlotResolution() {
|
||||
val rng = new Rng(42);
|
||||
|
||||
// A single horizontal slot at (0,0)
|
||||
val mask = Masker.Clues.parse("1 \n");
|
||||
val slots = Masker.slots(mask.c(), EN);
|
||||
|
||||
System.out.println("[DEBUG_LOG] \n--- Single Slot Resolution ---");
|
||||
if (slots.length > 0) {
|
||||
measureFill(rng, slots, mask.toGrid(), "Single Slot");
|
||||
} else {
|
||||
System.out.println("[DEBUG_LOG] Error: No slots found in mask.");
|
||||
}
|
||||
}
|
||||
|
||||
private void measureFill(Rng rng, Slotinfo[] slots, Grid grid, String label) {
|
||||
long t0 = System.currentTimeMillis();
|
||||
int iterations = 1;
|
||||
long totalNodes = 0;
|
||||
long totalBacktracks = 0;
|
||||
int successCount = 0;
|
||||
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
// Reset assignments for each iteration
|
||||
for (Slotinfo s : slots) s.assign().w = 0;
|
||||
|
||||
val result = fillMask(rng, slots, grid.copy(), false);
|
||||
if (result.ok()) successCount++;
|
||||
totalNodes += result.nodes();
|
||||
totalBacktracks += result.backtracks();
|
||||
}
|
||||
long t1 = System.currentTimeMillis();
|
||||
double totalDuration = (t1 - t0) / 1000.0;
|
||||
|
||||
System.out.printf(Locale.ROOT, "[DEBUG_LOG] %s: %d/%d SUCCESS | avg nodes=%d | avg backtracks=%d | total time=%.3fs%n",
|
||||
label, successCount, iterations, totalNodes / iterations, totalBacktracks / iterations, totalDuration);
|
||||
}
|
||||
|
||||
private void visualizeSlots(Slotinfo[] slots) {
|
||||
int R = SwedishGenerator.R;
|
||||
int C = SwedishGenerator.C;
|
||||
char[][] display = new char[R][C];
|
||||
for (int r = 0; r < R; r++) Arrays.fill(display[r], ' ');
|
||||
|
||||
for (Slotinfo slot : slots) {
|
||||
int key = slot.key();
|
||||
int dir = Masker.Slot.dir(key);
|
||||
int clueIdx = Masker.Slot.clueIndex(key);
|
||||
|
||||
int cr = SwedishGenerator.IT[clueIdx].r();
|
||||
int cc = SwedishGenerator.IT[clueIdx].c();
|
||||
|
||||
// User requested: aAAAA for a four letter to RIGHT clue slot.
|
||||
// SwedishGenerator: 1=RIGHT, 0=DOWN, 2=UP, 3=LEFT
|
||||
char clueChar;
|
||||
char slotChar;
|
||||
switch (dir) {
|
||||
case 1:
|
||||
clueChar = 'a';
|
||||
slotChar = 'A';
|
||||
break; // RIGHT
|
||||
case 0:
|
||||
clueChar = 'b';
|
||||
slotChar = 'B';
|
||||
break; // DOWN
|
||||
case 2:
|
||||
clueChar = 'c';
|
||||
slotChar = 'C';
|
||||
break; // UP
|
||||
case 3:
|
||||
clueChar = 'd';
|
||||
slotChar = 'D';
|
||||
break; // LEFT
|
||||
default:
|
||||
clueChar = '?';
|
||||
slotChar = '?';
|
||||
}
|
||||
|
||||
display[cr][cc] = clueChar;
|
||||
|
||||
Masker.Slot.from(slot.key(), slot.lo(), slot.hi(), null).walk().forEach(idx -> {
|
||||
int r = SwedishGenerator.IT[idx].r();
|
||||
int c = SwedishGenerator.IT[idx].c();
|
||||
if (display[r][c] == ' ' || (display[r][c] >= 'A' && display[r][c] <= 'D')) {
|
||||
if (display[r][c] != ' ' && display[r][c] != slotChar) {
|
||||
display[r][c] = '+'; // Intersection
|
||||
} else {
|
||||
display[r][c] = slotChar;
|
||||
}
|
||||
}
|
||||
long t1 = System.currentTimeMillis();
|
||||
double totalDuration = (t1 - t0) / 1000.0;
|
||||
|
||||
System.out.printf(Locale.ROOT, "[DEBUG_LOG] Size %d: %d/%d SUCCESS | avg nodes=%d | avg backtracks=%d | total time=%.3fs%n",
|
||||
size, successCount, iterations, totalNodes / iterations, totalBacktracks / iterations, totalDuration);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (int r = 0; r < R; r++) {
|
||||
System.out.println("[DEBUG_LOG] " + new String(display[r]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user