Python module providing tools for cubing algorithm manipulations.
pip install cubing-algs
- Parse and validate Rubik's cube algorithm notation
- Transform algorithms (mirror, compress, rotate, etc.)
- Calculate metrics (HTM, QTM, STM, ETM, QSTM)
- Support for wide moves, slice moves, and rotations
- Japanese notation support
from cubing_algs.parsing import parse_moves
from cubing_algs.transform.mirror import mirror_moves
from cubing_algs.transform.size import expand_moves
algo = parse_moves("F R U2 F'")
print(algo.transform(mirror_moves, expand_moves))
# F U U R' F'
Parse a string of moves into an Algorithm
object:
from cubing_algs.parsing import parse_moves
# Basic parsing
algo = parse_moves("R U R' U'")
# Parsing multiple formats
algo = parse_moves("R U R` U`") # Backtick notation
algo = parse_moves("R U R3 U3") # 3 for inverse
algo = parse_moves("R1 U1 R3 U3") # 1 for single moves
algo = parse_moves("R:U:R':U'") # With colons
algo = parse_moves("R(U)R'[U']") # With brackets/parentheses
# Parse CFOP style (removes starting/ending U/y rotations)
from cubing_algs.parsing import parse_moves_cfop
algo = parse_moves_cfop("y U R U R' U'") # Will remove the initial y
Apply various transformations to algorithms:
from cubing_algs.parsing import parse_moves
from cubing_algs.transform.mirror import mirror_moves
from cubing_algs.transform.size import compress_moves
from cubing_algs.transform.size import expand_moves
from cubing_algs.transform.japanese import japanese_moves
from cubing_algs.transform.japanese import unjapanese_moves
from cubing_algs.transform.rotation import remove_final_rotations
from cubing_algs.transform.slice import reslice_moves
from cubing_algs.transform.slice import unslice_wide_moves
from cubing_algs.transform.symmetry import (
symmetry_m_moves,
symmetry_s_moves,
symmetry_e_moves,
symmetry_c_moves
)
from cubing_algs.transform.offset import (
offset_x_moves,
offset_y_moves,
offset_z_moves
)
from cubing_algs.transform.degrip import (
degrip_x_moves,
degrip_y_moves,
degrip_z_moves,
degrip_full_moves
)
algo = parse_moves("R U R' U'")
# Mirror an algorithm
mirrored = algo.transform(mirror_moves) # U' R U' R'
# Compression/Expansion
compressed = algo.transform(compress_moves) # Optimize with cancellations
expanded = algo.transform(expand_moves) # Convert double moves to single pairs
# Japanese notation
japanese = algo.transform(japanese_moves) # Convert to Rw notation
unjapanese = algo.transform(unjapanese_moves) # Convert to r notation
# Remove final rotations
clean = algo.transform(remove_final_rotations) # Remove trailing x, y, z moves
# Slice moves
wide = algo.transform(unslice_wide_moves) # M -> r' R, S -> f F', E -> u' U
resliced = algo.transform(reslice_moves) # L' R -> M x, etc.
# Symmetry
m_sym = algo.transform(symmetry_m_moves) # M-slice symmetry (L<->R)
s_sym = algo.transform(symmetry_s_moves) # S-slice symmetry (F<->B)
e_sym = algo.transform(symmetry_e_moves) # E-slice symmetry (U<->D)
c_sym = algo.transform(symmetry_c_moves) # Combined M and S symmetry
# Offset (change viewpoint)
x_offset = algo.transform(offset_x_moves) # As if rotated with x
y_offset = algo.transform(offset_y_moves) # As if rotated with y
z_offset = algo.transform(offset_z_moves) # As if rotated with z
# Degrip (move rotations to the end)
x_degrip = algo.transform(degrip_x_moves) # Move x rotations to the end
y_degrip = algo.transform(degrip_y_moves) # Move y rotations to the end
z_degrip = algo.transform(degrip_z_moves) # Move z rotations to the end
full_degrip = algo.transform(degrip_full_moves) # Move all rotations to the end
Compute algorithm metrics:
from cubing_algs.parsing import parse_moves
algo = parse_moves("R U R' U' R' F R2 U' R' U' R U R' F'")
# Access metrics
print(algo.metrics)
# {
# 'rotations': 0,
# 'outer_moves': 14,
# 'inner_moves': 0,
# 'htm': 14,
# 'qtm': 16,
# 'stm': 14,
# 'etm': 14,
# 'qstm': 16,
# 'generators': ['R', 'U', 'F']
# }
# Individual metrics
print(f"HTM: {algo.metrics['htm']}")
print(f"QTM: {algo.metrics['qtm']}")
print(f"STM: {algo.metrics['stm']}")
print(f"ETM: {algo.metrics['etm']}")
print(f"QSTM: {algo.metrics['qstm']}")
print(f"Generators: {', '.join(algo.metrics['generators'])}")
The Move
class represents a single move:
from cubing_algs.move import Move
move = Move("R")
move2 = Move("R2")
move3 = Move("R'")
wide = Move("r")
japanese = Move("Rw")
rotation = Move("x")
# Properties
print(move.base_move) # R
print(move.modifier) # ''
# Checking move type
print(move.is_rotation_move) # False
print(move.is_outer_move) # True
print(move.is_inner_move) # False
print(move.is_wide_move) # False
# Checking modifiers
print(move.is_clockwise) # True
print(move.is_counter_clockwise) # False
print(move.is_double) # False
# Transformations
print(move.inverted) # R'
print(move.doubled) # R2
print(wide.japanesed) # Rw
print(japanese.unjapanesed) # r
The module provides several optimization functions to simplify algorithms:
from cubing_algs.parsing import parse_moves
from cubing_algs.transform.optimize import (
optimize_repeat_three_moves,
optimize_do_undo_moves,
optimize_double_moves,
optimize_triple_moves
)
algo = parse_moves("R R R")
optimized1 = algo.transform(optimize_repeat_three_moves) # R'
algo = parse_moves("R R'")
optimized2 = algo.transform(optimize_do_undo_moves) # (empty)
algo = parse_moves("R R")
optimized3 = algo.transform(optimize_double_moves) # R2
algo = parse_moves("R R2")
optimized4 = algo.transform(optimize_triple_moves) # R'
Multiple transformations can be chained together:
from cubing_algs.parsing import parse_moves
from cubing_algs.transform.mirror import mirror_moves
from cubing_algs.transform.size import compress_moves
from cubing_algs.transform.symmetry import symmetry_m_moves
algo = parse_moves("R U R' U' R' F R F'")
result = algo.transform(mirror_moves, compress_moves, symmetry_m_moves)
# Same as:
# result = algo.transform(mirror_moves)
# result = result.transform(compress_moves)
# result = result.transform(symmetry_m_moves)
Chained transformations can be run until a fixed point:
from cubing_algs.transform.optimize import optimize_do_undo_moves
from cubing_algs.transform.optimize import optimize_double_moves
algo = parse_moves("R R F F' R2 U F2")
result = algo.transform(optimize_do_undo_moves, optimize_double_moves)
# R2 R2 U F2
algo = parse_moves("R R F F' R2 U F2")
result = algo.transform(optimize_do_undo_moves, optimize_double_moves, to_fixpoint=True)
# U F2
The module calculates the following metrics:
- HTM (Half Turn Metric): Counts quarter turns as 1, half turns as 1
- QTM (Quarter Turn Metric): Counts quarter turns as 1, half turns as 2
- STM (Slice Turn Metric): Counts both face turns and slice moves as 1
- ETM (Execution Turn Metric): Counts all moves including rotations
- QSTM (Quarter Slice Turn Metric): Counts quarter turns as 1, slice quarter turns as 1, half turns as 2
from cubing_algs.parsing import parse_moves
from cubing_algs.transform.mirror import mirror_moves
oll = parse_moves("r U R' U' r' F R F'")
oll_mirror = oll.transform(mirror_moves)
print(oll_mirror) # F' R' F r U R U' r'
from cubing_algs.parsing import parse_moves
from cubing_algs.transform.japanese import japanese_moves
algo = parse_moves("r U R' U' r' F R F'")
japanese = algo.transform(japanese_moves)
print(japanese) # Rw U R' U' Rw' F R F'
from cubing_algs.parsing import parse_moves
from cubing_algs.transform.size import compress_moves
algo = parse_moves("R U U U R' R R F F' F F")
compressed = algo.transform(compress_moves)
print(compressed) # R U' R2 F2
from cubing_algs.parsing import parse_moves
from cubing_algs.transform.offset import offset_y_moves
algo = parse_moves("R U R' U'")
y_rotated = algo.transform(offset_y_moves)
print(y_rotated) # F R F' R'
from cubing_algs.parsing import parse_moves
from cubing_algs.transform.degrip import degrip_y_moves
algo = parse_moves("y F R U R' U' F'")
degripped = algo.transform(degrip_y_moves)
print(degripped) # R F R F' R' y