sourcelibs/Eigencube/src/Eigencube.xtl
1⍝# Eigencube: a Rubik's cube as rotation matrices, turned by matrix
2⍝# products and solved by search. A port of Steffen Smolka's
3⍝# eigencube.py (github.com/smolkaj/eigencube, MIT). Each of the 26
4⍝# cubelets is the integer point c of {-1, 0, 1}^3 where it sits in the
5⍝# solved cube and carries a rotation matrix R: it sits now at R c. A
6⍝# quarter turn of the face with outward normal v moves the cubelets
7⍝# with v . (R c) > 0, multiplying R by one rotation matrix. A batch of
8⍝# cubes is one array, n by 26 by 3 by 3, so all twelve turns of every
9⍝# cube in a search are two matrix products.
10⍝# Import it with an alias of your choice: "ec:" u_se< "Eigencube".
11⍝# Put libs/Eigencube/src on XETAL_PATH ("just path"); the reference is libs/Eigencube/docs.
12⍝# Names with l: are exported; those under h: are private to this file.
13⍝#
14⍝# Moves are numbered 1 to 12: move 2f-1 turns face f clockwise as seen
15⍝# facing it, move 2f counterclockwise, the faces in the order of
16⍝# ec:faces, "UDFBRL" (up +z, front +x, right +y); ec:n_ame writes them
17⍝# in the usual notation (U, U', D, ...) and ec:p_arse reads it.
18
19⍝## The cube
20
21⍝# The 27 integer points of {-1, 0, 1}^3, one per row (x slowest).
22pts ← (o̲\ 3 3 3 e̲ncode o̲ffsets 27) − 1
23⍝# The 26 cubelets, each named by its place in the solved cube: every
24⍝# point but the hidden center.
25C ← (0 < '+ r̲/₂ a̲bs pts) r̲eplicate pts
26⍝# Each cubelet's 1-norm, its number of stickers: 1 center, 2 edge,
27⍝# 3 corner.
28kind ← '+ r̲/₂ a̲bs C
29⍝# Each cubelet's layer in the solved cube: 1 top, 0 middle, -1 bottom.
30z ← 3 s̲elect₂ C
31⍝# The 3-by-3 identity.
32I ← 3 3 r̲eshape 1 0 0 0 1 0 0 0 1
33⍝# The solved cube: every cubelet unrotated, a batch of one, 1 26 3 3.
34ˡsolved ← 1 26 3 3 r̲eshape I
35
36⍝## The twelve quarter turns
37
38⍝# The six faces' outward normals, named U D F B R L: up is +z, front
39⍝# +x, right +y. Faces 2a-1 and 2a share axis a.
40N ← 6 3 r̲eshape 0 0 1 0 0 -1 1 0 0 -1 0 0 0 1 0 0 -1 0
41⍝# The face names, in the order of N.
42ˡfaces ← "UDFBRL"
43⍝# The move vectors: move 2f-1 turns face f clockwise as seen facing
44⍝# it, move 2f counterclockwise.
45V ← 2 r̲eplicate N
46⍝# Each move's direction: 1 clockwise, -1 counterclockwise.
47dir ← 12 r̲eshape 1 -1
48⍝# The cross-product matrices, flattened: row k holds minus the
49⍝# Levi-Civita symbol eps(i, j, k) over (i, j), so that row v of
50⍝# V '+ '× i̲nner E is [v]x, the matrix with [v]x u = v x u.
51E ← 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
52⍝# The twelve rotation matrices, flattened, one per row: a quarter
53⍝# turn clockwise about v (seen from v) is v v^T - [v]x, Rodrigues'
54⍝# formula at -90 degrees; counterclockwise flips the sign of [v]x.
55Mf ← (((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
56⍝# The twelve matrices stacked into one 36 by 3 matrix: rows 3m-2 to
57⍝# 3m are move m's. One product with it turns a batch every way at once.
58M ← 36 3 r̲eshape Mf
59
60⍝# h:t_urns R: every quarter turn applied to every 3-by-3 matrix of R,
61⍝# a batch of n (n 3 3); the result is 12 n 3 3, turn first. The batch
62⍝# is laid out 3 n 3, so one matrix product with M turns them all.
63ʰt̲urns ← { R →
64 n ← t̲ally R
65 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
66}
67
68⍝# h:p_os S: where each cubelet of each cube of the batch S (n 26 3 3)
69⍝# sits, R c; the result is n 26 3.
70ʰp̲os ← { S → '+ r̲/₄ S × (s̲hape S) r̲eshape 2 1 3 t̲ranspose 3 26 3 r̲eshape C }
71
72⍝# h:c_hildren S: all twelve turns of every cube of the batch S, 12n
73⍝# cubes, move-major: cube r is move 1 + (r - 1) div n of cube
74⍝# 1 + (r - 1) mod n. A cubelet moves when the move vector's dot
75⍝# product with where it sits is positive (one matrix product for
76⍝# every move and cubelet); a moved cubelet's matrix is the turn's
77⍝# matrix times its own (the other product, in h:t_urns).
78ʰc̲hildren ← { S →
79 n ← t̲ally S
80 sh ← 12 c̲at n c̲at 26 3 3
81 sel ← 0 < V '+ '× i̲nner o̲\ ((n × 26) c̲at 3) r̲eshape ʰp̲os S
82 T ← sh r̲eshape ʰt̲urns ((n × 26) c̲at 3 3) r̲eshape S
83 S12 ← sh r̲eshape S
84 ((12 × n) c̲at 26 3 3) r̲eshape S12 + (sh r̲eshape 9 r̲eplicate r̲avel sel) × T − S12
85}
86
87⍝# l:t_urn m S: move m (1 to 12) applied to every cube of the batch S.
88⍝# >> "ec:" u_se< "Eigencube"
89⍝# >> ec:solved m_atch 4 '{ S -> 5 ec:t_urn S } p_ower ec:solved
90⍝# 1
91ˡt̲urn ← { m S → (((m − 1) × t̲ally S) + r̲ange t̲ally S) s̲elect ʰc̲hildren S }
92
93⍝# l:d_o ms S: the moves ms (a vector of 1 to 12) applied in order to
94⍝# every cube of the batch S.
95⍝# >> "ec:" u_se< "Eigencube"
96⍝# >> ec:solved m_atch 12 11 10 9 ec:d_o 9 10 11 12 ec:d_o ec:solved
97⍝# 1
98ˡd̲o ← { ms S →
99 0 = t̲ally ms ? S
100 (1 d̲rop ms) ˡd̲o (f̲irst ms) ˡt̲urn S
101}
102
103⍝## Stickers: the cube as six faces of nine colors
104
105⍝# Each face's two directions across the page, as a cube net draws
106⍝# it: rightward, then downward, seen from outside the face (U with F
107⍝# below it, D with F above it, the side faces with U above them).
108across ← 6 3 r̲eshape 0 1 0 0 1 0 0 1 0 0 -1 0 -1 0 0 1 0 0
109down ← 6 3 r̲eshape 1 0 0 -1 0 0 0 0 -1 0 0 -1 0 0 -1 0 0 -1
110
111⍝# l:s_tickers S: the colors of the cube S (a batch of one) as a 6 by 9
112⍝# matrix, a row per face (U D F B R L), its cells row by row as the
113⍝# face is seen; a color is the face of the solved cube it belongs to
114⍝# (1 to 6, the order of N). Cubelet k shows a sticker on face v when
115⍝# v . (R c) = 1; the sticker's color is the direction it faced in the
116⍝# solved cube, R^T v, so the colors are one more matrix product.
117ˡs̲tickers ← { S →
118 P ← 26 3 r̲eshape ʰp̲os S
119 on ← r̲avel 1 = N '+ '× i̲nner o̲\ P
120 cell ← (3 × 1 + down '+ '× i̲nner o̲\ P) + 2 + across '+ '× i̲nner o̲\ P
121 key ← r̲avel (10 × 6 26 r̲eshape 26 r̲eplicate r̲ange 6) + cell
122 home ← N '+ '× i̲nner 2 1 3 t̲ranspose 26 3 3 r̲eshape S
123 color ← N i̲ndexOf 156 3 r̲eshape home
124 6 9 r̲eshape (g̲rade on r̲eplicate key) s̲elect on r̲eplicate color
125}
126
127⍝## The 24 orientations, and how far each cubelet is from home
128
129⍝# h:g_row G: the rotations G (flattened rows of 9) and every turn of
130⍝# them, without repeats.
131ʰ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 }
132⍝# The cube's 24 rotations, flattened, one per row: what the twelve
133⍝# turns reach from the identity (three rounds reach them all).
134G ← 3 'ʰg̲row p̲ower 1 9 r̲eshape I
135⍝# The turns' Cayley table: row m, column g is the index in G of the
136⍝# rotation that move m makes of rotation g.
137Tm ← 12 24 r̲eshape G i̲ndexOf 288 9 r̲eshape ʰt̲urns 24 3 3 r̲eshape G
138
139⍝# h:o_rient S: the index in G of each cubelet's rotation, n 26.
140ʰo̲rient ← { S → ((t̲ally S) c̲at 26) r̲eshape G i̲ndexOf ((26 × t̲ally S) c̲at 9) r̲eshape S }
141
142⍝# Where each cubelet sits in each rotation, 26 24: the index in pts
143⍝# of G c, from one matrix product of every rotation with every name.
144at ← 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
145⍝# Which points each move turns, 12 27: the dot product of the move
146⍝# vector with the point is positive.
147layer ← 1 × 0 < V '+ '× i̲nner o̲\ pts
148
149⍝# h:n_ext O: h:c_hildren for cubes given by their orientations O (n 26),
150⍝# as orientations (12n 26): the same turns, read from the two tables
151⍝# the matrices made (at, layer and the Cayley table Tm), so the search
152⍝# moves 26 small integers per cube instead of 26 matrices.
153ʰn̲ext ← { O →
154 n ← t̲ally O
155 m ← (n × 26) r̲eplicate o̲ffsets 12
156 o ← (12 × n × 26) r̲eshape r̲avel O
157 P ← ((r̲avel O) + 24 × (n × 26) r̲eshape o̲ffsets 26) s̲elect r̲avel at
158 sel ← ((27 × m) + (12 × n × 26) r̲eshape P) s̲elect r̲avel layer
159 ((12 × n) c̲at 26) r̲eshape o + sel × (((24 × m) + o) s̲elect r̲avel Tm) − o
160}
161
162⍝# h:r_elax d: a table of distances (cubelet by rotation) relaxed along
163⍝# every turn until it stops changing, so d[k; g] becomes the fewest
164⍝# quarter turns that take rotation g to one where cubelet k is done.
165ʰr̲elax ← { d →
166 e ← d m̲in 1 + 'm̲in r̲/₂ 26 12 24 r̲eshape (r̲avel Tm) s̲elect₂ d
167 e m̲atch d ? d
168 ʰr̲elax e
169}
170⍝# h:d_istances done: the distance table from a 26 by 24 table of 1s
171⍝# where the cubelet counts as done in that rotation.
172ʰd̲istances ← { done → ʰr̲elax 99 × 1 − done }
173
174⍝# How far column j of each rotation is from e_j, 24 3.
175colDev ← '+ r̲/₂ a̲bs (24 3 3 r̲eshape G) − 24 3 3 r̲eshape I
176⍝# Solved distances, 26 24 (eigencube.py's min_moves_to_solved): a
177⍝# cubelet is solved when its rotation fixes each axis it has stickers
178⍝# on, the nonzero coordinates of its name.
179Dsolved ← ʰd̲istances 0 = (a̲bs C) '+ '× i̲nner o̲\ colDev
180⍝# l:s_olved? S: whether every cubelet of the cube S (a batch of one) is
181⍝# solved (eigencube.py's is_cube_solved): a center may still be turned
182⍝# about its own axis, which no sticker shows.
183⍝# >> "ec:" u_se< "Eigencube"
184⍝# >> (ec:s_olved? ec:solved) c_at ec:s_olved? 1 ec:t_urn ec:solved
185⍝# 1 0
186ˡs̲olved? ← { S → 0 = '+ r̲/ ((r̲avel ʰo̲rient S) + 24 × o̲ffsets 26) s̲elect r̲avel Dsolved }
187⍝# How far each rotation moves each home point, 24 26.
188posDev ← '+ r̲/₂ a̲bs (24 3 26 r̲eshape (72 3 r̲eshape G) '+ '× i̲nner o̲\ C) − 24 3 26 r̲eshape o̲\ C
189⍝# Place distances, 26 24 (eigencube.py's min_moves_to_position): a
190⍝# cubelet is in place when R c = c, its stickers perhaps twisted.
191Dplace ← ʰd̲istances 0 = o̲\ posDev
192⍝# Whether each rotation keeps the bottom face (yellow) down: R fixes z.
193yellowDown ← 1 × 0 = 3 s̲elect₂ colDev
194
195⍝## The stages: goals and heuristics as matrix products
196
197⍝# The cubelet sets the stages count over, one per row: 1 none, 2 top
198⍝# edges, 3 top layer, 4 top and middle layers, 5 bottom edges,
199⍝# 6 bottom corners, 7 middle layer, 8 bottom layer but its corners,
200⍝# 9 all but the bottom corners.
201sets ← 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
202
203⍝# h:s_pec s: stage s (1 to 29) as 7 rows of 3. A goal is three counts
204⍝# at least their thresholds: count c is how many cubelets of set
205⍝# row1[c] are solved, plus of set row2[c] have yellow down, plus of
206⍝# set row3[c] are in place; row 6 holds the thresholds. The heuristic
207⍝# is a sum of three terms, term t (sum of sqrt d)^2 / row7[t], the
208⍝# sum over set row4[t] of solved distances and over set row5[t] of
209⍝# place distances (eigencube.py's p-norms with p = 1/2).
210⍝# Stages 1-17 solve the top and middle layers a cubelet at a time,
211⍝# 18-25 the bottom edges (yellow down, then solved), 26-29 put the
212⍝# bottom corners in place.
213ʰs̲pec ← { s →
214 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
215 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
216 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
217 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
218}
219
220⍝# h:o_f r spec: the cubelet sets named by row r of a stage's spec, as
221⍝# the columns of a 26 by 3 matrix of 1s and 0s.
222ʰo̲f ← { r spec → o̲\ (r s̲elect spec) s̲elect sets }
223
224⍝# h:r_elevant spec: the cubelets a stage looks at, 1s and 0s over the
225⍝# 26: those of every set its goal and heuristic name (rows 1 to 5).
226ʰr̲elevant ← { spec → 0 < '+ r̲/ (r̲avel 5 t̲ake spec) s̲elect sets }
227⍝# h:p_roject rel O: the orientations O with every cubelet the stage
228⍝# does not look at (rel 0) set to 1, the identity. A turn moves each
229⍝# cubelet by its own position and rotation alone, never by the others,
230⍝# so cubes that differ only there are one state to the stage's search.
231ʰp̲roject ← { rel O → 1 + (O − 1) × (s̲hape O) r̲eshape rel }
232
233⍝# h:s_core spec O: each cube's search score, from its cubelets'
234⍝# orientations O (n 26, h:o_rient): -1 for a cube that meets the
235⍝# stage's goal, else its heuristic, a Float per cube.
236ʰs̲core ← { spec O →
237 n ← t̲ally O
238 o ← r̲avel O
239 ix ← o + 24 × (n × 26) r̲eshape o̲ffsets 26
240 ds ← (n c̲at 26) r̲eshape ix s̲elect r̲avel Dsolved
241 dp ← (n c̲at 26) r̲eshape ix s̲elect r̲avel Dplace
242 yd ← (n c̲at 26) r̲eshape o s̲elect yellowDown
243 cnt ← ((ds = 0) '+ '× i̲nner (1 ʰo̲f spec)) + (yd '+ '× i̲nner (2 ʰo̲f spec)) + (dp = 0) '+ '× i̲nner (3 ʰo̲f spec)
244 goal ← '∧ r̲/₂ cnt ≥ (n c̲at 3) r̲eshape 6 s̲elect spec
245 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
246 h ← (q × q) '+ '× i̲nner 1 ÷ 7 s̲elect spec
247 ((f̲loat n̲ot goal) × h + 1.0) − 1.0
248}
249
250⍝## Search: A*, a batch of cubes at a time
251
252⍝# How many of the best open cubes each step of the search expands.
253batch ← 256
254⍝# How much more the heuristic weighs than the moves made (weighted A*).
255greed ← 1.0
256
257⍝# l:i_nverse m: the move that undoes move m (0, no move, has none).
258ˡi̲nverse ← { m → m + (2 × m m̲od 2) − 1 }
259⍝# h:f_ace m: the face move m turns, 1 to 6 (0 for no move).
260ʰf̲ace ← { m → (m + 1) d̲iv 2 }
261
262⍝# h:c_ubes O: the cubes whose cubelets' orientations are O (n 26), their
263⍝# rotation matrices looked up in G, n 26 3 3.
264ʰc̲ubes ← { O → ((t̲ally O) c̲at 26 3 3) r̲eshape (r̲avel O) s̲elect G }
265⍝# h:k_ey O: each cube's key, n 2: its 26 orientations (1 to 24) as two
266⍝# 13-digit numbers in base 24, so one i_ndexOf finds a cube.
267ʰ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 }
268
269⍝# The macros: the solver's moves for the middle and bottom layers,
270⍝# each a fixed sequence of turns. 1 to 3: D, D' and D2. 4 to 8: the
271⍝# classic sequences for the last layer, mirrored from the top to the
272⍝# bottom (U and D swap, R and L swap): F L D L' D' F' flips two bottom
273⍝# edges, Sune (L D L' D L D2 L') and its mirror cycle edges and twist
274⍝# corners, D L D' R' D L' D' R and its inverse cycle three bottom
275⍝# corners. 9 to 16: a middle edge brought up from the bottom into each
276⍝# of the four slots, from either side (U R U' R' U' F' U F and its
277⍝# mirror, mirrored the same way and turned about the vertical axis).
278⍝# Each keeps the top layer whole, and each of 1 to 8 the top two
279⍝# layers, so a search over them builds on what is solved (as the
280⍝# endgame's twist does in eigencube.py).
281macros ← (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
282⍝# The macros the middle layer's stages search over: D turns and the
283⍝# insertions.
284middle ← 1 2 3 9 10 11 12 13 14 15 16
285⍝# The macros the bottom layer's stages search over: D turns and the
286⍝# last-layer sequences.
287bottom ← 1 2 3 4 5 6 7 8
288
289⍝# h:a_ctions hybrid s: what stage s searches over: none for the twelve
290⍝# turns (all stages when hybrid is 0, eigencube.py's search alone; the
291⍝# top layer always), else the middle or the bottom layer's macros.
292ʰa̲ctions ← { hybrid s →
293 (hybrid = 0) ∨ s ≤ 9 ? o̲ffsets 0
294 s ≤ 17 ? middle
295 bottom
296}
297
298⍝# h:a_long ms O: the moves ms applied in order to the cubes O (n 26),
299⍝# as orientations.
300ʰa̲long ← { ms O →
301 0 = t̲ally ms ? O
302 (1 d̲rop ms) ʰa̲long ((((f̲irst ms) − 1) × t̲ally O) + r̲ange t̲ally O) s̲elect ʰn̲ext O
303}
304⍝# h:m_acros set O: every macro of the set (indices into macros)
305⍝# applied to every cube of O, macro-major as h:n_ext orders the turns.
306ʰm̲acros ← { set O →
307 0 = t̲ally set ? 0 26 r̲eshape 0
308 ((d̲isclose (f̲irst set) s̲elect macros) ʰa̲long O) c̲at (1 d̲rop set) ʰm̲acros O
309}
310⍝# h:e_xpand set O: the cubes one action from O: the twelve turns for an
311⍝# empty set, else the set's macros.
312ʰe̲xpand ← { set O →
313 0 = t̲ally set ? ʰn̲ext O
314 set ʰm̲acros O
315}
316⍝# h:f_latten acts: macros (indices into macros) as the moves they make.
317ʰf̲latten ← { acts →
318 0 = t̲ally acts ? o̲ffsets 0
319 (d̲isclose (f̲irst acts) s̲elect macros) c̲at ʰf̲latten 1 d̲rop acts
320}
321
322⍝# h:s_tep spec table: one step of the search. The table holds every cube
323⍝# found, a row each: its key, its orientations, the cube it was reached
324⍝# from, the move, its moves from the start g and its priority
325⍝# g + greed * h; then the open cubes, best first. A step takes the best
326⍝# batch open cubes, turns each every way at once, drops a move that
327⍝# undoes the last one or turns two opposite faces out of order (they
328⍝# commute; eigencube.py prunes the same two), and adds the cubes not
329⍝# found before. It stops on a cube that meets the goal, giving the
330⍝# moves to it.
331ʰs̲tep ← { (spec, set) (keys, O, par, mv, g, f, open) →
332 0 = t̲ally open ? "stuck" ⎕S̲IGNAL "the search ran out of cubes"
333 pick ← (batch m̲in t̲ally open) t̲ake open
334 n ← t̲ally pick
335 r ← o̲ffsets n × (t̲ally set) + 12 × 0 = t̲ally set
336 cm ← 1 + r d̲iv n
337 cp ← (1 + r m̲od n) s̲elect pick
338 last ← cp s̲elect mv
339 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
340 KO ← (ʰr̲elevant spec) ʰp̲roject ok s̲elect set ʰe̲xpand pick s̲elect O
341 cp ← ok s̲elect cp
342 cm ← ok s̲elect cm
343 sc ← spec ʰs̲core KO
344 hit ← w̲here sc < 0
345 0 < t̲ally hit ? ((par, mv) ʰt̲race f̲irst hit s̲elect cp) c̲at f̲irst hit s̲elect cm
346 KK ← ʰk̲ey KO
347 new ← w̲here ((KK i̲ndexOf KK) = r̲ange t̲ally KK) ∧ (keys i̲ndexOf KK) > t̲ally keys
348 ng ← 1 + new s̲elect cp s̲elect g
349 f2 ← f c̲at (f̲loat ng) + greed × new s̲elect sc
350 rest ← (batch d̲rop open) c̲at (t̲ally keys) + r̲ange t̲ally new
351 (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)
352}
353
354⍝# h:t_race (par, mv) i: the moves from the start (cube 1) to cube i, by
355⍝# following each cube back to the one it was reached from.
356ʰt̲race ← { (par, mv) i →
357 1 = i ? o̲ffsets 0
358 ((par, mv) ʰt̲race i s̲elect par) c̲at i s̲elect mv
359}
360
361⍝# h:s_earch (spec, set) S: the moves taking the cube S (a batch of one)
362⍝# to the stage's goal, found by h:s_tep from a table holding S alone,
363⍝# over the twelve turns or the set's macros.
364ʰs̲earch ← { (spec, set) S →
365 O ← (ʰr̲elevant spec) ʰp̲roject ʰo̲rient S
366 0 > f̲irst spec ʰs̲core O ? o̲ffsets 0
367 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)
368 0 = t̲ally set ? acts
369 ʰf̲latten acts s̲elect set
370}
371
372⍝## Solving
373
374⍝# h:s_tage hybrid (S, ms, lens): the next search stage run on the cube
375⍝# S, its moves added to the moves so far ms and its length to the
376⍝# stage lengths (h:a_ctions says what it searches over).
377ʰs̲tage ← { hybrid (S, ms, lens) →
378 s ← 1 + t̲ally lens
379 next ← ((ʰs̲pec s), hybrid ʰa̲ctions s) ʰs̲earch S
380 (next ˡd̲o S, ms c̲at next, lens c̲at t̲ally next)
381}
382
383⍝# The endgame's twist: L' U' L U, twice (eigencube.py's routine).
384twist ← 12 2 11 1 12 2 11 1
385⍝# The bottom turn eigencube.py makes between corners, D' here.
386bottomTurn ← 1 r̲eshape 4
387
388⍝# h:o_riented? S: whether the corner at front-left-bottom, (1 -1 -1),
389⍝# has its yellow sticker down, so that turns of the bottom alone solve
390⍝# it (eigencube.py's is_corner_oriented): its rotation fixes z.
391ʰo̲riented? ← { S →
392 k ← (26 3 r̲eshape ʰp̲os S) i̲ndexOf 1 -1 -1
393 (k s̲elect r̲avel ʰo̲rient S) s̲elect yellowDown
394}
395
396⍝# h:c_orner (S, ms): twist the front-left-bottom corner until it is
397⍝# oriented, then turn the bottom to bring the next corner there.
398ʰc̲orner ← { (S, ms) →
399 ʰo̲riented? S ? (bottomTurn ˡd̲o S, ms c̲at bottomTurn)
400 ʰc̲orner (twist ˡd̲o S, ms c̲at twist)
401}
402
403⍝# h:f_inish (S, ms): turn the bottom until the cube is solved.
404ʰf̲inish ← { (S, ms) →
405 ˡs̲olved? S ? (S, ms)
406 ʰf̲inish (bottomTurn ˡd̲o S, ms c̲at bottomTurn)
407}
408
409⍝# l:s_olve hybrid S: a solution of the cube S (a batch of one), after
410⍝# eigencube.py's solve: 29 search stages (the top and middle layers a
411⍝# cubelet at a time, then the bottom edges and the bottom corners'
412⍝# places), then the endgame's corner twists. With hybrid 1 the middle
413⍝# and bottom stages search over macros, as a person solving by hand
414⍝# uses known sequences (seconds); with 0 every stage searches the
415⍝# twelve turns, eigencube.py's search alone (minutes in X_eTaL). The
416⍝# moves, each stage's length, and the endgame's length.
417ˡs̲olve ← { hybrid S →
418 (S1, ms, lens) ← 29 '{ st → hybrid ʰs̲tage st } p̲ower (S, o̲ffsets 0, o̲ffsets 0)
419 (_, all) ← ʰf̲inish 4 'ʰc̲orner p̲ower (S1, ms)
420 (all, lens, (t̲ally all) − t̲ally ms)
421}
422
423⍝## Notation
424
425⍝# l:n_ame ms: moves as text in the usual notation, U for the top face
426⍝# clockwise, U' counterclockwise, separated by spaces.
427ˡn̲ame ← { ms →
428 n ← t̲ally ms
429 marks ← (1 + ms m̲od 2) s̲elect "' "
430 txt ← r̲avel o̲\ (3 c̲at n) r̲eshape ((ʰf̲ace ms) s̲elect ˡfaces) c̲at marks c̲at n r̲eshape " "
431 -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
432}
433
434⍝# ec:p_arse t: moves written in the usual notation as numbers: each
435⍝# letter of ec:faces is a move, counterclockwise when a ' follows it;
436⍝# anything else between moves is ignored. The inverse of ec:n_ame.
437⍝# >> "ec:" u_se< "Eigencube"
438⍝# >> ec:p_arse "U R' F B'"
439⍝# 1 10 5 8
440⍝# >> (ec:p_arse ec:n_ame 3 12 7) m_atch 3 12 7
441⍝# 1
442ˡp̲arse ← { t →
443 at ← w̲here t m̲ember? ˡfaces
444 f ← ˡfaces i̲ndexOf at s̲elect t
445 (2 × f) − n̲ot ((1 + at) s̲elect t c̲at " ") = f̲irst "'"
446}