librarylibs/Eigencube/src/Eigencube.xtl
Eigencube: a Rubik's cube as rotation matrices, turned by matrix products and solved by search. A port of Steffen Smolka's eigencube.py (github.com/smolkaj/eigencube, MIT). Each of the 26 cubelets is the integer point c of {-1, 0, 1}^3 where it sits in the solved cube and carries a rotation matrix R: it sits now at R c. A quarter turn of the face with outward normal v moves the cubelets with v . (R c) > 0, multiplying R by one rotation matrix. A batch of cubes is one array, n by 26 by 3 by 3, so all twelve turns of every cube in a search are two matrix products. Import it with an alias of your choice: "ec:" u_se< "Eigencube". Put libs/Eigencube/src on XETAL_PATH ("just path"); the reference is libs/Eigencube/docs. Names with l: are exported; those under h: are private to this file.
Moves are numbered 1 to 12: move 2f-1 turns face f clockwise as seen facing it, move 2f counterclockwise, the faces in the order of ec:faces, "UDFBRL" (up +z, front +x, right +y); ec:n_ame writes them in the usual notation (U, U', D, ...) and ec:p_arse reads it.
The cube
pts : Int
The 27 integer points of {-1, 0, 1}^3, one per row (x slowest).
pts ← (o̲\ 3 3 3 e̲ncode o̲ffsets 27) − 1
C : Int
The 26 cubelets, each named by its place in the solved cube: every point but the hidden center.
C ← (0 < '+ r̲/₂ a̲bs pts) r̲eplicate pts
kind : Int
Each cubelet's 1-norm, its number of stickers: 1 center, 2 edge, 3 corner.
kind ← '+ r̲/₂ a̲bs C
z : Int
Each cubelet's layer in the solved cube: 1 top, 0 middle, -1 bottom.
z ← 3 s̲elect₂ C
ˡsolved : Int
The solved cube: every cubelet unrotated, a batch of one, 1 26 3 3.
ˡsolved ← 1 26 3 3 r̲eshape I
The twelve quarter turns
N : Int
The six faces' outward normals, named U D F B R L: up is +z, front +x, right +y. Faces 2a-1 and 2a share axis a.
N ← 6 3 r̲eshape 0 0 1 0 0 -1 1 0 0 -1 0 0 0 1 0 0 -1 0
V : Int
The move vectors: move 2f-1 turns face f clockwise as seen facing it, move 2f counterclockwise.
V ← 2 r̲eplicate N
dir : Int
Each move's direction: 1 clockwise, -1 counterclockwise.
dir ← 12 r̲eshape 1 -1
E : Int
The cross-product matrices, flattened: row k holds minus the
Levi-Civita symbol eps(i, j, k) over (i, j), so that row v of
V '+ '× i̲nner E is [v]x, the matrix with [v]x u = v x u.
E ← 3 9 r̲eshape 0 0 0 0 0 -1 0 1 0 0 0 1 0 0 0 -1 0 0 0 -1 0 1 0 0 0 0 0
Mf : Int
The twelve rotation matrices, flattened, one per row: a quarter turn clockwise about v (seen from v) is v v^T - [v]x, Rodrigues' formula at -90 degrees; counterclockwise flips the sign of [v]x.
Mf ← (((3 r̲eplicate 1 2 3) s̲elect₂ V) × (9 r̲eshape 1 2 3) s̲elect₂ V) − (12 9 r̲eshape 9 r̲eplicate dir) × V '+ '× i̲nner E
M : Int
The twelve matrices stacked into one 36 by 3 matrix: rows 3m-2 to 3m are move m's. One product with it turns a batch every way at once.
M ← 36 3 r̲eshape Mf
ʰt̲urns : Int -> Int
h:t_urns R: every quarter turn applied to every 3-by-3 matrix of R, a batch of n (n 3 3); the result is 12 n 3 3, turn first. The batch is laid out 3 n 3, so one matrix product with M turns them all.
ʰt̲urns ← { R → n ← t̲ally R 1 3 2 4 t̲ranspose (12 3 c̲at n c̲at 3) r̲eshape M '+ '× i̲nner 2 1 3 t̲ranspose R }
ʰp̲os : Int -> Int
h:p_os S: where each cubelet of each cube of the batch S (n 26 3 3) sits, R c; the result is n 26 3.
ʰp̲os ← { S → '+ r̲/₄ S × (s̲hape S) r̲eshape 2 1 3 t̲ranspose 3 26 3 r̲eshape C }
ʰc̲hildren : Int -> Int
h:c_hildren S: all twelve turns of every cube of the batch S, 12n cubes, move-major: cube r is move 1 + (r - 1) div n of cube 1 + (r - 1) mod n. A cubelet moves when the move vector's dot product with where it sits is positive (one matrix product for every move and cubelet); a moved cubelet's matrix is the turn's matrix times its own (the other product, in h:t_urns).
ʰc̲hildren ← { S → n ← t̲ally S sh ← 12 c̲at n c̲at 26 3 3 sel ← 0 < V '+ '× i̲nner o̲\ ((n × 26) c̲at 3) r̲eshape ʰp̲os S T ← sh r̲eshape ʰt̲urns ((n × 26) c̲at 3 3) r̲eshape S S12 ← sh r̲eshape S ((12 × n) c̲at 26 3 3) r̲eshape S12 + (sh r̲eshape 9 r̲eplicate r̲avel sel) × T − S12 }
ˡt̲urn : Int -> Int -> Int
l:t_urn m S: move m (1 to 12) applied to every cube of the batch S.
ᵉᶜ⁼u̲se< "Eigencube"
ᵉᶜsolved m̲atch 4 '{ S → 5 ᵉᶜt̲urn S } p̲ower ᵉᶜsolved 1
ˡt̲urn ← { m S → (((m − 1) × t̲ally S) + r̲ange t̲ally S) s̲elect ʰc̲hildren S }
ˡd̲o : Int -> Int -> Int
l:d_o ms S: the moves ms (a vector of 1 to 12) applied in order to every cube of the batch S.
ᵉᶜ⁼u̲se< "Eigencube"
ᵉᶜsolved m̲atch 12 11 10 9 ᵉᶜd̲o 9 10 11 12 ᵉᶜd̲o ᵉᶜsolved 1
ˡd̲o ← { ms S → 0 = t̲ally ms ? S (1 d̲rop ms) ˡd̲o (f̲irst ms) ˡt̲urn S }
Stickers: the cube as six faces of nine colors
across : Int
Each face's two directions across the page, as a cube net draws it: rightward, then downward, seen from outside the face (U with F below it, D with F above it, the side faces with U above them).
across ← 6 3 r̲eshape 0 1 0 0 1 0 0 1 0 0 -1 0 -1 0 0 1 0 0
ˡs̲tickers : Int -> Int
l:s_tickers S: the colors of the cube S (a batch of one) as a 6 by 9 matrix, a row per face (U D F B R L), its cells row by row as the face is seen; a color is the face of the solved cube it belongs to (1 to 6, the order of N). Cubelet k shows a sticker on face v when v . (R c) = 1; the sticker's color is the direction it faced in the solved cube, R^T v, so the colors are one more matrix product.
ˡs̲tickers ← { S → P ← 26 3 r̲eshape ʰp̲os S on ← r̲avel 1 = N '+ '× i̲nner o̲\ P cell ← (3 × 1 + down '+ '× i̲nner o̲\ P) + 2 + across '+ '× i̲nner o̲\ P key ← r̲avel (10 × 6 26 r̲eshape 26 r̲eplicate r̲ange 6) + cell home ← N '+ '× i̲nner 2 1 3 t̲ranspose 26 3 3 r̲eshape S color ← N i̲ndexOf 156 3 r̲eshape home 6 9 r̲eshape (g̲rade on r̲eplicate key) s̲elect on r̲eplicate color }
The 24 orientations, and how far each cubelet is from home
ʰg̲row : Int -> Int
h:g_row G: the rotations G (flattened rows of 9) and every turn of them, without repeats.
ʰg̲row ← { G → u̲nique G c̲at ((12 × t̲ally G) c̲at 9) r̲eshape ʰt̲urns ((t̲ally G) c̲at 3 3) r̲eshape G }
G : Int
The cube's 24 rotations, flattened, one per row: what the twelve turns reach from the identity (three rounds reach them all).
G ← 3 'ʰg̲row p̲ower 1 9 r̲eshape I
Tm : Int
The turns' Cayley table: row m, column g is the index in G of the rotation that move m makes of rotation g.
Tm ← 12 24 r̲eshape G i̲ndexOf 288 9 r̲eshape ʰt̲urns 24 3 3 r̲eshape G
ʰo̲rient : Int -> Int
h:o_rient S: the index in G of each cubelet's rotation, n 26.
ʰo̲rient ← { S → ((t̲ally S) c̲at 26) r̲eshape G i̲ndexOf ((26 × t̲ally S) c̲at 9) r̲eshape S }
at : Int
Where each cubelet sits in each rotation, 26 24: the index in pts of G c, from one matrix product of every rotation with every name.
at ← 26 24 r̲eshape pts i̲ndexOf 624 3 r̲eshape 2 3 1 t̲ranspose 24 3 26 r̲eshape (72 3 r̲eshape G) '+ '× i̲nner o̲\ C
layer : Int
Which points each move turns, 12 27: the dot product of the move vector with the point is positive.
layer ← 1 × 0 < V '+ '× i̲nner o̲\ pts
ʰn̲ext : Int -> Int
h:n_ext O: h:c_hildren for cubes given by their orientations O (n 26), as orientations (12n 26): the same turns, read from the two tables the matrices made (at, layer and the Cayley table Tm), so the search moves 26 small integers per cube instead of 26 matrices.
ʰn̲ext ← { O → n ← t̲ally O m ← (n × 26) r̲eplicate o̲ffsets 12 o ← (12 × n × 26) r̲eshape r̲avel O P ← ((r̲avel O) + 24 × (n × 26) r̲eshape o̲ffsets 26) s̲elect r̲avel at sel ← ((27 × m) + (12 × n × 26) r̲eshape P) s̲elect r̲avel layer ((12 × n) c̲at 26) r̲eshape o + sel × (((24 × m) + o) s̲elect r̲avel Tm) − o }
ʰr̲elax : Num a => a -> a
h:r_elax d: a table of distances (cubelet by rotation) relaxed along every turn until it stops changing, so d[k; g] becomes the fewest quarter turns that take rotation g to one where cubelet k is done.
ʰr̲elax ← { d → e ← d m̲in 1 + 'm̲in r̲/₂ 26 12 24 r̲eshape (r̲avel Tm) s̲elect₂ d e m̲atch d ? d ʰr̲elax e }
ʰd̲istances : Num a => a -> a
h:d_istances done: the distance table from a 26 by 24 table of 1s where the cubelet counts as done in that rotation.
ʰd̲istances ← { done → ʰr̲elax 99 × 1 − done }
colDev : Int
How far column j of each rotation is from e_j, 24 3.
colDev ← '+ r̲/₂ a̲bs (24 3 3 r̲eshape G) − 24 3 3 r̲eshape I
Dsolved : Int
Solved distances, 26 24 (eigencube.py's min_moves_to_solved): a cubelet is solved when its rotation fixes each axis it has stickers on, the nonzero coordinates of its name.
Dsolved ← ʰd̲istances 0 = (a̲bs C) '+ '× i̲nner o̲\ colDev
ˡs̲olved? : Truthy a => Int -> a
l:s_olved? S: whether every cubelet of the cube S (a batch of one) is solved (eigencube.py's is_cube_solved): a center may still be turned about its own axis, which no sticker shows.
ᵉᶜ⁼u̲se< "Eigencube"
(ᵉᶜs̲olved? ᵉᶜsolved) c̲at ᵉᶜs̲olved? 1 ᵉᶜt̲urn ᵉᶜsolved 1 0
ˡs̲olved? ← { S → 0 = '+ r̲/ ((r̲avel ʰo̲rient S) + 24 × o̲ffsets 26) s̲elect r̲avel Dsolved }
posDev : Int
How far each rotation moves each home point, 24 26.
posDev ← '+ r̲/₂ a̲bs (24 3 26 r̲eshape (72 3 r̲eshape G) '+ '× i̲nner o̲\ C) − 24 3 26 r̲eshape o̲\ C
Dplace : Int
Place distances, 26 24 (eigencube.py's min_moves_to_position): a cubelet is in place when R c = c, its stickers perhaps twisted.
Dplace ← ʰd̲istances 0 = o̲\ posDev
yellowDown : Int
Whether each rotation keeps the bottom face (yellow) down: R fixes z.
yellowDown ← 1 × 0 = 3 s̲elect₂ colDev
The stages: goals and heuristics as matrix products
sets : Int
The cubelet sets the stages count over, one per row: 1 none, 2 top edges, 3 top layer, 4 top and middle layers, 5 bottom edges, 6 bottom corners, 7 middle layer, 8 bottom layer but its corners, 9 all but the bottom corners.
sets ← 9 26 r̲eshape (26 r̲eshape 0) c̲at ((z = 1) ∧ kind = 2) c̲at (z = 1) c̲at (z ≥ 0) c̲at ((z = -1) ∧ kind = 2) c̲at ((z = -1) ∧ kind = 3) c̲at (z = 0) c̲at ((z = -1) ∧ kind < 3) c̲at n̲ot (z = -1) ∧ kind = 3
ʰs̲pec : Num a => a -> a
h:s_pec s: stage s (1 to 29) as 7 rows of 3. A goal is three counts at least their thresholds: count c is how many cubelets of set row1[c] are solved, plus of set row2[c] have yellow down, plus of set row3[c] are in place; row 6 holds the thresholds. The heuristic is a sum of three terms, term t (sum of sqrt d)^2 / row7[t], the sum over set row4[t] of solved distances and over set row5[t] of place distances (eigencube.py's p-norms with p = 1/2). Stages 1-17 solve the top and middle layers a cubelet at a time, 18-25 the bottom edges (yellow down, then solved), 26-29 put the bottom corners in place.
ʰs̲pec ← { s → s ≤ 9 ? 7 3 r̲eshape (2 3 4 1 1 1 1 1 1 3 1 1 1 1 1) c̲at (4 m̲in s) c̲at s c̲at s c̲at 8 1 1 s ≤ 17 ? 7 3 r̲eshape (2 3 4 1 1 1 1 1 1 4 1 1 1 1 1 4 9) c̲at s c̲at 4 1 1 s ≤ 25 ? 7 3 r̲eshape (4 1 5 1 5 1 1 1 1 9 1 1 1 1 1 17) c̲at (4 m̲in s − 17) c̲at (s − 21) c̲at 3 1 1 7 3 r̲eshape (4 5 1 1 1 1 1 1 6 3 7 8 1 1 6 17 4) c̲at (s − 25) c̲at 5 3 8 }
ʰo̲f : Int -> Int -> Int
h:o_f r spec: the cubelet sets named by row r of a stage's spec, as the columns of a 26 by 3 matrix of 1s and 0s.
ʰo̲f ← { r spec → o̲\ (r s̲elect spec) s̲elect sets }
ʰr̲elevant : Truthy a => Int -> a
h:r_elevant spec: the cubelets a stage looks at, 1s and 0s over the 26: those of every set its goal and heuristic name (rows 1 to 5).
ʰr̲elevant ← { spec → 0 < '+ r̲/ (r̲avel 5 t̲ake spec) s̲elect sets }
ʰp̲roject : Num a => a -> a -> a
h:p_roject rel O: the orientations O with every cubelet the stage does not look at (rel 0) set to 1, the identity. A turn moves each cubelet by its own position and rotation alone, never by the others, so cubes that differ only there are one state to the stage's search.
ʰp̲roject ← { rel O → 1 + (O − 1) × (s̲hape O) r̲eshape rel }
ʰs̲core : Int -> Int -> Float
h:s_core spec O: each cube's search score, from its cubelets' orientations O (n 26, h:o_rient): -1 for a cube that meets the stage's goal, else its heuristic, a Float per cube.
ʰs̲core ← { spec O → n ← t̲ally O o ← r̲avel O ix ← o + 24 × (n × 26) r̲eshape o̲ffsets 26 ds ← (n c̲at 26) r̲eshape ix s̲elect r̲avel Dsolved dp ← (n c̲at 26) r̲eshape ix s̲elect r̲avel Dplace yd ← (n c̲at 26) r̲eshape o s̲elect yellowDown cnt ← ((ds = 0) '+ '× i̲nner (1 ʰo̲f spec)) + (yd '+ '× i̲nner (2 ʰo̲f spec)) + (dp = 0) '+ '× i̲nner (3 ʰo̲f spec) goal ← '∧ r̲/₂ cnt ≥ (n c̲at 3) r̲eshape 6 s̲elect spec q ← (((f̲loat ds) ^ 0.5) '+ '× i̲nner f̲loat 4 ʰo̲f spec) + ((f̲loat dp) ^ 0.5) '+ '× i̲nner f̲loat 5 ʰo̲f spec h ← (q × q) '+ '× i̲nner 1 ÷ 7 s̲elect spec ((f̲loat n̲ot goal) × h + 1.0) − 1.0 }
Search: A*, a batch of cubes at a time
batch : Int
How many of the best open cubes each step of the search expands.
batch ← 256
greed : Float
How much more the heuristic weighs than the moves made (weighted A*).
greed ← 1.0
ˡi̲nverse : Int -> Int
l:i_nverse m: the move that undoes move m (0, no move, has none).
ˡi̲nverse ← { m → m + (2 × m m̲od 2) − 1 }
ʰf̲ace : Int -> Int
h:f_ace m: the face move m turns, 1 to 6 (0 for no move).
ʰf̲ace ← { m → (m + 1) d̲iv 2 }
ʰc̲ubes : Int -> Int
h:c_ubes O: the cubes whose cubelets' orientations are O (n 26), their rotation matrices looked up in G, n 26 3 3.
ʰc̲ubes ← { O → ((t̲ally O) c̲at 26 3 3) r̲eshape (r̲avel O) s̲elect G }
ʰk̲ey : Num a => a -> a
h:k_ey O: each cube's key, n 2: its 26 orientations (1 to 24) as two 13-digit numbers in base 24, so one i_ndexOf finds a cube.
ʰk̲ey ← { O → o̲\ (2 c̲at t̲ally O) r̲eshape (24 d̲ecode o̲\ 13 t̲ake₂ O − 1) c̲at 24 d̲ecode o̲\ -13 t̲ake₂ O − 1 }
macros : Box Int
The macros: the solver's moves for the middle and bottom layers, each a fixed sequence of turns. 1 to 3: D, D' and D2. 4 to 8: the classic sequences for the last layer, mirrored from the top to the bottom (U and D swap, R and L swap): F L D L' D' F' flips two bottom edges, Sune (L D L' D L D2 L') and its mirror cycle edges and twist corners, D L D' R' D L' D' R and its inverse cycle three bottom corners. 9 to 16: a middle edge brought up from the bottom into each of the four slots, from either side (U R U' R' U' F' U F and its mirror, mirrored the same way and turned about the vertical axis). Each keeps the top layer whole, and each of 1 to 8 the top two layers, so a search over them builds on what is solved (as the endgame's twist does in eigencube.py).
macros ← (e̲nclose 1 r̲eshape 3) c̲at (e̲nclose 1 r̲eshape 4) c̲at (e̲nclose 3 3) c̲at (e̲nclose 5 11 3 12 4 6) c̲at (e̲nclose 11 3 12 3 11 3 3 12) c̲at (e̲nclose 11 3 3 12 4 11 4 12) c̲at (e̲nclose 3 11 4 10 3 12 4 9) c̲at (e̲nclose 10 3 11 4 9 3 12 4) c̲at (e̲nclose 3 11 4 12 4 6 3 5) c̲at (e̲nclose 3 5 4 6 4 10 3 9) c̲at (e̲nclose 3 9 4 10 4 8 3 7) c̲at (e̲nclose 3 7 4 8 4 12 3 11) c̲at (e̲nclose 4 10 3 9 3 5 4 6) c̲at (e̲nclose 4 8 3 7 3 9 4 10) c̲at (e̲nclose 4 12 3 11 3 7 4 8) c̲at e̲nclose 4 6 3 5 3 11 4 12
middle : Int
The macros the middle layer's stages search over: D turns and the insertions.
middle ← 1 2 3 9 10 11 12 13 14 15 16
bottom : Int
The macros the bottom layer's stages search over: D turns and the last-layer sequences.
bottom ← 1 2 3 4 5 6 7 8
ʰa̲ctions : (Num a, Num b) => a -> b -> Int
h:a_ctions hybrid s: what stage s searches over: none for the twelve turns (all stages when hybrid is 0, eigencube.py's search alone; the top layer always), else the middle or the bottom layer's macros.
ʰa̲ctions ← { hybrid s → (hybrid = 0) ∨ s ≤ 9 ? o̲ffsets 0 s ≤ 17 ? middle bottom }
ʰa̲long : Int -> Int -> Int
h:a_long ms O: the moves ms applied in order to the cubes O (n 26), as orientations.
ʰa̲long ← { ms O → 0 = t̲ally ms ? O (1 d̲rop ms) ʰa̲long ((((f̲irst ms) − 1) × t̲ally O) + r̲ange t̲ally O) s̲elect ʰn̲ext O }
ʰm̲acros : Int -> Int -> Int
h:m_acros set O: every macro of the set (indices into macros)
applied to every cube of O, macro-major as h:n_ext orders the turns.
ʰm̲acros ← { set O → 0 = t̲ally set ? 0 26 r̲eshape 0 ((d̲isclose (f̲irst set) s̲elect macros) ʰa̲long O) c̲at (1 d̲rop set) ʰm̲acros O }
ʰe̲xpand : Int -> Int -> Int
h:e_xpand set O: the cubes one action from O: the twelve turns for an empty set, else the set's macros.
ʰe̲xpand ← { set O → 0 = t̲ally set ? ʰn̲ext O set ʰm̲acros O }
ʰf̲latten : Int -> Int
h:f_latten acts: macros (indices into macros) as the moves they make.
ʰf̲latten ← { acts → 0 = t̲ally acts ? o̲ffsets 0 (d̲isclose (f̲irst acts) s̲elect macros) c̲at ʰf̲latten 1 d̲rop acts }
ʰs̲tep : Num a => (Int, Int) -> (Int, Int, Int, Int, a, Float, Int) -> Int
h:s_tep spec table: one step of the search. The table holds every cube
found, a row each: its key, its orientations, the cube it was reached
from, the move, its moves from the start g and its priority
g + greed * h; then the open cubes, best first. A step takes the best
batch open cubes, turns each every way at once, drops a move that
undoes the last one or turns two opposite faces out of order (they
commute; eigencube.py prunes the same two), and adds the cubes not
found before. It stops on a cube that meets the goal, giving the
moves to it.
ʰs̲tep ← { (spec, set) (keys, O, par, mv, g, f, open) → 0 = t̲ally open ? "stuck" ⎕S̲IGNAL "the search ran out of cubes" pick ← (batch m̲in t̲ally open) t̲ake open n ← t̲ally pick r ← o̲ffsets n × (t̲ally set) + 12 × 0 = t̲ally set cm ← 1 + r d̲iv n cp ← (1 + r m̲od n) s̲elect pick last ← cp s̲elect mv ok ← w̲here (0 < t̲ally set) ∨ n̲ot (cm = ˡi̲nverse last) ∨ (ʰf̲ace last) = 1 + (ʰf̲ace cm) × 1 = 2 m̲od ʰf̲ace cm KO ← (ʰr̲elevant spec) ʰp̲roject ok s̲elect set ʰe̲xpand pick s̲elect O cp ← ok s̲elect cp cm ← ok s̲elect cm sc ← spec ʰs̲core KO hit ← w̲here sc < 0 0 < t̲ally hit ? ((par, mv) ʰt̲race f̲irst hit s̲elect cp) c̲at f̲irst hit s̲elect cm KK ← ʰk̲ey KO new ← w̲here ((KK i̲ndexOf KK) = r̲ange t̲ally KK) ∧ (keys i̲ndexOf KK) > t̲ally keys ng ← 1 + new s̲elect cp s̲elect g f2 ← f c̲at (f̲loat ng) + greed × new s̲elect sc rest ← (batch d̲rop open) c̲at (t̲ally keys) + r̲ange t̲ally new (spec, set) ʰs̲tep (keys c̲at new s̲elect KK, O c̲at new s̲elect KO, par c̲at new s̲elect cp, mv c̲at new s̲elect cm, g c̲at ng, f2, (g̲rade rest s̲elect f2) s̲elect rest) }
ʰt̲race : (Int, Int) -> Int -> Int
h:t_race (par, mv) i: the moves from the start (cube 1) to cube i, by following each cube back to the one it was reached from.
ʰt̲race ← { (par, mv) i → 1 = i ? o̲ffsets 0 ((par, mv) ʰt̲race i s̲elect par) c̲at i s̲elect mv }
ʰs̲earch : (Int, Int) -> Int -> Int
h:s_earch (spec, set) S: the moves taking the cube S (a batch of one) to the stage's goal, found by h:s_tep from a table holding S alone, over the twelve turns or the set's macros.
ʰs̲earch ← { (spec, set) S → O ← (ʰr̲elevant spec) ʰp̲roject ʰo̲rient S 0 > f̲irst spec ʰs̲core O ? o̲ffsets 0 acts ← (spec, set) ʰs̲tep (ʰk̲ey O, O, 1 r̲eshape 0, 1 r̲eshape 0, 1 r̲eshape 0, 1 r̲eshape 0.0, 1 r̲eshape 1) 0 = t̲ally set ? acts ʰf̲latten acts s̲elect set }
Solving
ʰs̲tage : Num a => a -> (Int, Int, Int) -> (Int, Int, Int)
h:s_tage hybrid (S, ms, lens): the next search stage run on the cube S, its moves added to the moves so far ms and its length to the stage lengths (h:a_ctions says what it searches over).
ʰs̲tage ← { hybrid (S, ms, lens) → s ← 1 + t̲ally lens next ← ((ʰs̲pec s), hybrid ʰa̲ctions s) ʰs̲earch S (next ˡd̲o S, ms c̲at next, lens c̲at t̲ally next) }
twist : Int
The endgame's twist: L' U' L U, twice (eigencube.py's routine).
twist ← 12 2 11 1 12 2 11 1
bottomTurn : Int
The bottom turn eigencube.py makes between corners, D' here.
bottomTurn ← 1 r̲eshape 4
ʰo̲riented? : Int -> Int
h:o_riented? S: whether the corner at front-left-bottom, (1 -1 -1), has its yellow sticker down, so that turns of the bottom alone solve it (eigencube.py's is_corner_oriented): its rotation fixes z.
ʰo̲riented? ← { S → k ← (26 3 r̲eshape ʰp̲os S) i̲ndexOf 1 -1 -1 (k s̲elect r̲avel ʰo̲rient S) s̲elect yellowDown }
ʰc̲orner : (Int, Int) -> (Int, Int)
h:c_orner (S, ms): twist the front-left-bottom corner until it is oriented, then turn the bottom to bring the next corner there.
ʰc̲orner ← { (S, ms) → ʰo̲riented? S ? (bottomTurn ˡd̲o S, ms c̲at bottomTurn) ʰc̲orner (twist ˡd̲o S, ms c̲at twist) }
ʰf̲inish : (Int, Int) -> (Int, Int)
h:f_inish (S, ms): turn the bottom until the cube is solved.
ʰf̲inish ← { (S, ms) → ˡs̲olved? S ? (S, ms) ʰf̲inish (bottomTurn ˡd̲o S, ms c̲at bottomTurn) }
ˡs̲olve : Num a => a -> Int -> (Int, Int, Int)
l:s_olve hybrid S: a solution of the cube S (a batch of one), after eigencube.py's solve: 29 search stages (the top and middle layers a cubelet at a time, then the bottom edges and the bottom corners' places), then the endgame's corner twists. With hybrid 1 the middle and bottom stages search over macros, as a person solving by hand uses known sequences (seconds); with 0 every stage searches the twelve turns, eigencube.py's search alone (minutes in X_eTaL). The moves, each stage's length, and the endgame's length.
ˡs̲olve ← { hybrid S → (S1, ms, lens) ← 29 '{ st → hybrid ʰs̲tage st } p̲ower (S, o̲ffsets 0, o̲ffsets 0) (_, all) ← ʰf̲inish 4 'ʰc̲orner p̲ower (S1, ms) (all, lens, (t̲ally all) − t̲ally ms) }
Notation
ˡn̲ame : Int -> Char
l:n_ame ms: moves as text in the usual notation, U for the top face clockwise, U' counterclockwise, separated by spaces.
ˡn̲ame ← { ms → n ← t̲ally ms marks ← (1 + ms m̲od 2) s̲elect "' " txt ← r̲avel o̲\ (3 c̲at n) r̲eshape ((ʰf̲ace ms) s̲elect ˡfaces) c̲at marks c̲at n r̲eshape " " -1 d̲rop (r̲avel o̲\ (3 c̲at n) r̲eshape (n r̲eshape 1) c̲at (marks = f̲irst "'") c̲at n r̲eshape 1) r̲eplicate txt }
ˡp̲arse : Char -> Int
ec:p_arse t: moves written in the usual notation as numbers: each letter of ec:faces is a move, counterclockwise when a ' follows it; anything else between moves is ignored. The inverse of ec:n_ame.
ᵉᶜ⁼u̲se< "Eigencube"
ᵉᶜp̲arse "U R' F B'" 1 10 5 8
(ᵉᶜp̲arse ᵉᶜn̲ame 3 12 7) m̲atch 3 12 7 1
ˡp̲arse ← { t → at ← w̲here t m̲ember? ˡfaces f ← ˡfaces i̲ndexOf at s̲elect t (2 × f) − n̲ot ((1 + at) s̲elect t c̲at " ") = f̲irst "'" }