Python API¶
solve()¶
The main entry point for solving puzzles programmatically.
Signature¶
Parameters¶
| Parameter | Type | Default | Description |
|---|---|---|---|
puzzle |
str |
required | 81-char puzzle string (0 or . for empty) |
max_level |
int |
99 |
Max technique level (1-7) |
no_oracle |
bool |
False |
Pure logic only — stop if stalled |
detail |
bool |
False |
Capture rich detail (candidates, explanations) |
gf2_extended |
bool |
False |
Use GF(2) Extended with probing + conjugates |
Returns¶
A dictionary with the following keys:
{
'success': bool, # True if puzzle fully solved
'stalled': bool, # True if engine stalled (with no_oracle=True)
'board': str, # Current board state (81 chars)
'solution': str, # Backtrack solution for verification
'n_steps': int, # Number of placement steps
'steps': list, # List of step dictionaries
'technique_counts': dict, # {technique_name: count}
'empty_remaining': int, # Unsolved cells (0 if success)
'rounds': int, # Solver rounds
'elim_events': list, # Elimination events (if detail=True)
}
Step Dictionary¶
Each entry in steps looks like:
{
'step': 42, # step number
'pos': 35, # cell position (0-80)
'digit': 7, # placed digit
'technique': 'crossHatch', # technique used
'cell': 'R4C9', # human-readable cell name
'round': 5, # which round this happened in
}
With detail=True, steps also include:
{
'cands_before': [3, 7, 9], # candidates before placement
'explanation': '...', # human-readable explanation
}
Examples¶
Basic solve:
from larsdoku_zs import solve
result = solve("4...3.......6..8..........1....5..9..8....6...7.2........1.27..5.3....4.9........")
print(f"Solved in {result['n_steps']} steps")
Pure logic with technique analysis:
result = solve(puzzle, no_oracle=True)
if result['success']:
for tech, count in sorted(result['technique_counts'].items(), key=lambda x: -x[1]):
print(f" {tech}: {count}")
else:
print(f"Stalled with {result['empty_remaining']} cells remaining")
Detailed solve with elimination tracking:
result = solve(puzzle, detail=True, no_oracle=True)
for step in result['steps']:
print(f" #{step['step']:3d} {step['cell']}={step['digit']} [{step['technique']}]")
if 'explanation' in step:
print(f" {step['explanation']}")
Batch processing:
from larsdoku_zs import solve
from larsdoku_zs.puzzles import TOP1465
pure = 0
for puzzle in TOP1465:
result = solve(puzzle, no_oracle=True)
if result['success']:
pure += 1
print(f"Pure logic: {pure}/{len(TOP1465)} ({100*pure/len(TOP1465):.1f}%)")
Puzzle Collections¶
FAMOUS_10¶
List of tuples: (name, author, year, puzzle_string)
from larsdoku_zs.puzzles import FAMOUS_10
for name, author, year, puzzle in FAMOUS_10:
print(f"{name} by {author} ({year})")
EXPERT_669¶
List of 81-character puzzle strings. 669 expert-level puzzles, box-shuffled.
TOP1465¶
List of 81-character puzzle strings (using . for empty cells). The canonical Stertenbrink/dukuso benchmark.
Web API (--serve)¶
When running larsdoku --serve, the engine exposes a REST API at POST /api/solve.
Solve Request¶
{
"puzzle": "800000000003600000070090200...",
"autotrust": true,
"level": 7,
"no_oracle": false,
"gf2": false,
"gf2x": false,
"preset": "expert",
"only": null,
"exclude": null,
"cell": null,
"path": false
}
All fields except puzzle are optional.
| Field | Type | Default | Description |
|---|---|---|---|
puzzle |
str |
required | 81-char puzzle string |
autotrust |
bool |
true |
Trust backtrack solution (enables DeepResonance) |
level |
int |
99 |
Max technique level (1-7) |
no_oracle |
bool |
false |
Pure logic only |
gf2 |
bool |
false |
Enable GF(2) Block Lanczos |
gf2x |
bool |
false |
Enable GF(2) Extended (implies gf2) |
preset |
str |
null |
"expert" or "wsrf" |
only |
str |
null |
Comma-separated technique list |
exclude |
str |
null |
Comma-separated exclusion list |
cell |
str |
null |
Cell query mode (e.g., "R3C5") |
path |
bool |
false |
Include technique path (with cell) |
Solve Response¶
{
"success": true,
"steps": [
{"step": 1, "pos": 14, "digit": 3, "technique": "crossHatch", "cell": "R2C6", "round": 1}
],
"technique_counts": {"crossHatch": 42, "nakedSingle": 9},
"elapsed_ms": 18.3,
"solution": "812753649943682175...",
"stalled": false,
"empty_remaining": 0,
"n_steps": 55,
"elim_events": [
{"round": 1, "technique": "SimpleColoring", "detail": "Simple Coloring: 5 eliminations", "count": 5}
]
}
Cell Query Response¶
When cell is provided, the API calls query_cell() instead:
{
"cell": "R3C5",
"answer": 9,
"technique": "nakedSingle",
"step": 48,
"reachable": true,
"candidates": [2, 9],
"path": [{"step": 1, "pos": 14, "digit": 3, "technique": "crossHatch", "cell": "R2C6", "round": 1}],
"elim_events": [...],
"solve_status": "solved",
"path_technique_counts": {"crossHatch": 22, "nakedSingle": 14},
"elapsed_ms": 526.2,
"message": "R3C5 = 9 via nakedSingle (step 48)"
}
Board Class¶
The Board class provides a higher-level API with zone intelligence, SIRO predictions, and cascade analysis.
Board.solve_cascade()¶
Cascade solver: classifies each step as bottleneck (L3+ technique) or cascade (L1-L2). Shows how the puzzle avalanches from a few hard moves.
b = Board("980700600700000090006050000...")
result = b.solve_cascade()
print(f"Bottleneck depth: {result['bottleneck_depth']}")
print(f"Cascade placements: {result['cascade_count']}")
print(f"Ratio: 1:{result['cascade_count'] // max(1, result['bottleneck_depth'])}")
for m in result['bottleneck_moves']:
print(f" {m['cell']}={m['digit']} via {m['technique']}")
On the 50 hardest puzzles, average bottleneck depth is 2.8 — just 3 hard moves, everything else cascades through singles.
Board.siro_solve()¶
Hybrid solver: techniques crack bottlenecks, SIRO predicts the rest.
b = Board("980700600700000090006050000...")
result = b.siro_solve()
print(f"Predictions: {result['correct']}/{result['total_predictions']} ({result['accuracy']:.1%})")
print(f"Propagated: {result['propagated']} cells")
Board.scandalous_exocet()¶
Post-solve validated Exocet scan. Solves with pure logic first, then checks if any Exocet patterns on the original board are valid.
b = Board("980700600750000040003080070...")
results = b.scandalous_exocet(preset='larstech')
for r in results:
print(f"{r['detail']}")
print(f"Valid: {r['valid']}")
Board.forge_permute()¶
Constellation Forge: generate unique puzzles via digit permutation. Takes a unique puzzle, permutes digits 1-9, returns up to 362,880 unique puzzles.
puzzles = Board.forge_permute(
"000060010000300007000001300007000080020400006100005900003050060800009500040200091",
count=10
)
for p in puzzles:
print(p)
Every output puzzle is guaranteed unique. The mask structure is preserved — only digit labels change.
Board.siro_predict()¶
Run SIRO cross-digit prediction on the current board state.
b = Board("980700600...")
preds = b.siro_predict()
for pos, info in preds.items():
print(f"R{pos//9+1}C{pos%9+1}: predict {info['digit']} (confidence {info['confidence']})")
Board.compute_zones()¶
Compute WSRF zone likely map for the current board.
Engine Access¶
For advanced usage, you can access the bitwise engine directly:
from larsdoku_zs.engine import BitBoard, propagate_l1l2, solve_backtrack
# Create a board
bb = BitBoard.from_string("4...3.......6..8..........1....")
# Run L1+L2 propagation
placements = propagate_l1l2(bb)
print(f"L1+L2 placed {len(placements)} digits, {bb.empty} remaining")
# Get backtrack solution (for verification)
solution = solve_backtrack("4...3.......6..8..........1....")
Warning
The engine API is lower-level and may change between versions. Use solve() for stable usage.