Files
puzzle-generator/src/test/java/puzzle/PerformanceTest.java
2026-01-23 11:58:57 +01:00

208 lines
7.5 KiB
Java

package puzzle;
import module java.base;
import anno.DictGen;
import anno.Dictionaries;
import lombok.val;
import org.junit.jupiter.api.Test;
import puzzle.Export.Clue;
import puzzle.Export.Signa;
import puzzle.Export.Puzzle;
import puzzle.SwedishGenerator.Rng;
import puzzle.SwedishGenerator.Slotinfo;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static precomp.Const9x8.Cell.*;
import static puzzle.SwedishGenerator.fillMask;
import static puzzle.dict800.DictData800.DICT800;
import static puzzle.dict900.DictData900.DICT900;
@Dictionaries({ @DictGen(
packageName = "puzzle.dict900",
className = "DictData900",
scv = "/home/mike/dev/puzzle-generator/nl_score_hints_v4.csv",
simpleMax = 900,
minLen = 2,
maxLen = 8
), @DictGen(
packageName = "puzzle.dict800",
className = "DictData800",
scv = "/home/mike/dev/puzzle-generator/nl_score_hints_v4.csv",
simpleMax = 800,
minLen = 2,
maxLen = 8
), @DictGen(
packageName = "puzzle.dict800_4",
className = "DictData800_4",
scv = "/home/mike/dev/puzzle-generator/nl_score_hints_v4.csv",
simpleMax = 800,
minLen = 2,
maxLen = 4
) })
public class PerformanceTest {
void main() {
testIncrementalComplexity();
}
@Test
void testPerformance() {
val rng = new Rng(42);
// 1. Stress test Clue Generation (Mask Generation)
System.out.println("[DEBUG_LOG] --- Mask Generation Performance ---");
var clueSizes = new int[]{ 20, 25, 30 };
var arr = new Clues[3];
var c = 0;
for (var size : clueSizes) {
var t0 = System.currentTimeMillis();
val masker = new Masker(rng, new int[Masker.STACK_SIZE], Clues.createEmpty());
// Increased population and generations for stress
arr[c++] = masker.generateMask(size, 200, 100, 50);
var t1 = System.currentTimeMillis();
var 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 ---");
c = 0;
for (var size : clueSizes) {
var t0 = System.currentTimeMillis();
// Try to fill multiple times to get a better average
var iterations = 10;
long totalNodes = 0;
long totalBacktracks = 0;
var successCount = 0;
for (var i = 0; i < iterations; i++) {
val slotInfo = Masker.slots(arr[c], DICT800);
var grid = Masker.grid(slotInfo);
val result = fillMask(rng, slotInfo, grid.lo, grid.hi, grid.g);
if (result.ok()) successCount++;
totalNodes += result.nodes();
totalBacktracks += result.backtracks();
}
c++;
var t1 = System.currentTimeMillis();
var 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);
}
}
@Test
void testIncrementalComplexity() {
// Use the complex mask from Main.java
var mask = Signa.of(
r0c0d1, r0c5d0, r0c6d0, r0c7d0, r0c8d0,
r1c0d1,
r2c0d0, r2c1d0, r2c3d0, r2c4d1,
r3c4d1,
r4c4d1,
r5c2d2, r5c4d1,
r6c2d1,
r7c0d2, r7c1d2, r7c2d1, r7c7d2, r7c8d2
);
val allSlots = Masker.slots(mask.c(), DICT900);
//mask.toGrid()
System.out.println("[DEBUG_LOG] \n--- Incremental Complexity Test ---");
System.out.println("[DEBUG_LOG] Full Slot Layout:");
visualizeSlots(allSlots);
for (var 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);
visualizeSlots(subset);
measureFill(new Rng(123 + i), subset, "Subset size " + i);
}
}
@Test
void testSingleSlotResolution() {
val rng = new Rng(42);
// A single horizontal slot at (0,0)
val mask = Signa.of(r0c0d1);
val slots = Masker.slots(mask.c(), DICT800);
System.out.println("[DEBUG_LOG] \n--- Single Slot Resolution ---");
if (slots.length > 0) {
measureFill(rng, slots, "Single Slot");
} else {
System.out.println("[DEBUG_LOG] Error: No slots found in mask.");
}
}
private void measureFill(Rng rng, Slotinfo[] slots, String label) {
var t0 = System.currentTimeMillis();
var iterations = 1;
long totalNodes = 0;
long totalBacktracks = 0;
var successCount = 0;
for (var i = 0; i < iterations; i++) {
// Reset assignments for each iteration
for (var s : slots) s.assign().w = 0;
var grid = Masker.grid(slots);
val result = fillMask(rng, slots, grid.lo, grid.hi, grid.g);
if (result.ok()) {
successCount++;
}
totalNodes += result.nodes();
totalBacktracks += result.backtracks();
}
var t1 = System.currentTimeMillis();
var 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);
visualizeSlots(slots);
}
private void visualizeSlots(Slotinfo[] slots) {
var R = Masker.R;
var C = Masker.C;
var display = new char[R][C];
for (var r = 0; r < R; r++) Arrays.fill(display[r], ' ');
for (var slot : slots) {
var key = slot.key();
var dir = Clue.from(Masker.Slot.dir(key));
var clueIdx = Masker.Slot.clueIndex(key);
var cr = Masker.IT[clueIdx].r();
var cc = Masker.IT[clueIdx].c();
// User requested: aAAAA for a four letter to RIGHT clue slot.
// SwedishGenerator: 1=RIGHT, 0=DOWN, 2=UP, 3=LEFT
var clueChar = dir.clueChar;
var slotChar = dir.slotChar;
display[cr][cc] = clueChar;
Puzzle.cellWalk(slot.key(), slot.lo(), slot.hi())
.skip(1)
.forEach(idx -> {
var r = Masker.IT[idx].r();
var c = Masker.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;
}
}
});
}
for (var r = 0; r < R; r++) {
System.out.println("[DEBUG_LOG] " + new String(display[r]));
}
}
}