introduce bitloops

This commit is contained in:
mike
2026-01-18 01:29:46 +01:00
parent 20617cda57
commit 112c16c525
7 changed files with 262 additions and 105 deletions

View File

@@ -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()));
}

View File

@@ -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]));
}
}
}