Skip to main content

rubik-solver-py

A pure-Python implementation of Kociemba's two-phase algorithm for solving the Rubik's Cube optimally (≤22 moves). Port of the TypeScript rubik-solver library.

Installation

pip install rubik-solver-py

Quick Start

from rubik_solver import Cube, init_solver, solve, scramble

# init_solver() pre-computes move and pruning tables (~30-60 s, one-time cost).
# Call it once at application startup before solving anything.
init_solver()

# Solve from a move sequence
cube = Cube().move("R U R' U' R' F R2 U' R' U' R U R' F'")
solution = solve(cube)
print(solution)  # e.g. "F R U R' U' F'"

# Generate a random scramble
print(scramble())

API Reference

init_solver()

Pre-computes all move and pruning tables required by the solver.

Must be called once before solve() or scramble().

Safe to call multiple times — subsequent calls are no-ops.


solve(cube, max_depth=22) → str | None

Solves the given Cube instance using Kociemba's two-phase algorithm.

Parameter Type Default Description
cube Cube The cube to solve
max_depth int 22 Maximum move count; returns Noneif exceeded

Returns a move string like "R U R' U'", or None if no solution was found.


scramble() → str

Returns a random scramble sequence as a move string.


Cube

The main cube class.

Method / Property Description
Cube() Creates a solved cube
.move(alg) Applies an algorithm string in place; returns self
.clone() Returns a deep copy
.is_solved() Returns Trueif the cube is solved
.as_string() Returns the 54-char facelet string
.randomize() Randomizes the cube in place; returns self
.verify() Returns Trueif valid, or an error string
Cube.from_string(s) Parses a 54-char facelet string into a Cube
Cube.random() Returns a new randomized Cube
Cube.inverse(alg) Inverts an algorithm string

Supported Move Notation

Faces: U R F D L B

Slice moves: E M S

Rotations: x y z

Wide moves: u r f d l b

Modifiers: ' (inverse), 2 (double)


Usage Examples

Build and verify a cube manually

from rubik_solver import Cube

# Solved cube
cube = Cube()
print(cube.is_solved())    # True

# Apply moves
cube.move("R U R' U'")
print(cube.is_solved())    # False

# Serialize to 54-char facelet string
print(cube.as_string())
# "UUUUUUUUURRRRRRRRRFFFFFFFFFDDDDDDDDDLLLLLLLLLBBBBBBBBB"

# Parse from a facelet string
cube2 = Cube.from_string("UUUUUUUUURRRRRRRRRFFFFFFFFFDDDDDDDDDLLLLLLLLLBBBBBBBBB")

# Invert an algorithm
print(Cube.inverse("R U R' U'"))  # "U R U' R'"

Solve a random cube

from rubik_solver import Cube, init_solver, solve

init_solver()
cube = Cube.random()
solution = solve(cube)
print(f"Solution ({len(solution.split())} moves): {solution}")

Validate a cube from facelet string

from rubik_solver import Cube

cube = Cube.from_string("UUUUUUUUURRRRRRRRRFFFFFFFFFDDDDDDDDDLLLLLLLLLBBBBBBBBB")
result = cube.verify()
if result is True:
    print("Cube is valid!")
else:
    print(f"Invalid: {result}")

Architecture

rubik_solver/
├── __init__.py        ← Public API exports
├── types.py           ← Enums (Center, Corner, Edge) and CubeState dataclass
├── constants.py       ← Table sizes, facelet mappings, base move data
├── math_utils.py      ← cnk, factorial, rotate_left, rotate_right
├── cube.py            ← Cube class (core logic, coordinates, serialization)
├── tables/
│   └── tables.py      ← Move table and pruning table generation
└── solver/
    ├── search_state.py ← SearchState used in phase 1 & 2 search
    └── solver.py       ← init_solver(), solve(), scramble(), two-phase search

Performance

Scenario Time
init_solver()— first ever call ~8 s (builds NumPy tables, writes cache)
init_solver()— subsequent calls ~15 ms (loads from ~/.cache/rubik_solver/)
solve()per cube ~0.1 – 1 s
scramble() ~0.5 – 2 s

Tables are cached automatically as .npy files. You can override the cache directory:

export RUBIK_SOLVER_CACHE_DIR=/path/to/your/cache

To force a rebuild:

from rubik_solver import clear_cache, init_solver
clear_cache()
init_solver(verbose=True)  # prints progress

Why not 10 ms like JavaScript?

V8 JIT-compiles the table construction loops. Python uses NumPy for vectorised BFS (fast) but the coordinate-computation inner loop still runs in CPython. The disk cache makes the difference negligible in practice — after the first run startup is ~15 ms regardless.

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

rubik_solver_py-0.1.1.tar.gz (15.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

rubik_solver_py-0.1.1-py3-none-any.whl (17.5 kB view details)

Uploaded Python 3

File details

Details for the file rubik_solver_py-0.1.1.tar.gz.

File metadata

  • Download URL: rubik_solver_py-0.1.1.tar.gz
  • Upload date:
  • Size: 15.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for rubik_solver_py-0.1.1.tar.gz
Algorithm Hash digest
SHA256 c604a3b7455095aa3bb5a6f76162f28f2c626713794fa0f43b544f02b6acebe7
MD5 a5ec4e243732fbe42a7cf83f57df74e0
BLAKE2b-256 f06848deb6786e8c92e927f7108716c163bc63582a5ca3e8a57b413517b2ba01

See more details on using hashes here.

File details

Details for the file rubik_solver_py-0.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for rubik_solver_py-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 461ac3c3fdf2aca63f70c3b00c36ec155f3c414d2a6cab5c0ae7b0d2cdecb1cc
MD5 7dadf2b2dac4be35566439b32d0679c1
BLAKE2b-256 c445107563e3a2d2dee6405e059a7568b76dfde92ecbb64a7fb14f16f7b0b943

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page