Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/interfaces/rubik.py
4048 views
1
r"""
2
3
Interface to two Rubik's cube solvers.
4
5
The first is by Michael Reid, and tries to find an optimal solution given
6
the cube's state, and may take a long time.
7
See http://www.math.ucf.edu/~reid/Rubik/optimal_solver.html
8
9
The second is by Eric Dietz, and uses a standard (?) algorithm to
10
solve the cube one level at a time. It is extremely fast, but often
11
returns a far from optimal solution.
12
See http://wrongway.org/?rubiksource
13
14
The third is by Dik Winter and implements Kociemba's algorithm which
15
finds reasonable solutions relatively quickly, and if it is kept running
16
will eventually find the optimal solution.
17
18
19
20
AUTHOR:
21
-- Optimal was written by Michael Reid <[email protected]> (2004)
22
-- Cubex was written by Eric Dietz <[email protected]> (2003)
23
-- Kociemba was written by Dik T. Winter <[email protected]> (1993)
24
-- Initial interface by Robert Bradshaw (2007-08)
25
"""
26
27
########################################################################
28
# Copyright (C) 2007 Robert Bradshaw <[email protected]>
29
#
30
# Distributed under the terms of the GNU General Public License (GPL)
31
#
32
# The full text of the GPL is available at:
33
#
34
# http://www.gnu.org/licenses/
35
########################################################################
36
37
import pexpect, time
38
import cleaner
39
40
from sage.groups.perm_gps.cubegroup import *
41
42
43
44
# Can't seem to find consistency in letter ordering
45
# between us and them... These are copied from the source.
46
optimal_solver_tokens = ["UF", "UR", "UB", "UL", \
47
"DF", "DR", "DB", "DL", \
48
"FR", "FL", "BR", "BL", \
49
"FU", "RU", "BU", "LU", \
50
"FD", "RD", "BD", "LD", \
51
"RF", "LF", "RB", "LB", \
52
"UFR", "URB", "UBL", "ULF", \
53
"DRF", "DFL", "DLB", "DBR", \
54
"FRU", "RBU", "BLU", "LFU", \
55
"RFD", "FLD", "LBD", "BRD", \
56
"RUF", "BUR", "LUB", "FUL", \
57
"FDR", "LDF", "BDL", "RDB"]
58
59
# The input format.
60
optimal_solver_format = "UF UR UB UL DF DR DB DL FR FL BR BL UFR URB UBL ULF DRF DFL DLB DBR"
61
62
class SingNot:
63
"""
64
This class is to resolve difference between various Singmaster notation.
65
Case is ignored, and the second and third letters may be swapped.
66
67
EXAMPLE:
68
sage: from sage.interfaces.rubik import SingNot
69
sage: SingNot("acb") == SingNot("ACB")
70
True
71
sage: SingNot("acb") == SingNot("bca")
72
False
73
"""
74
def __init__(self, s):
75
self.rep = s
76
self.canonical = (s[0] + "".join(sorted(list(s[1:])))).lower()
77
def __eq__(self, other):
78
return isinstance(other, SingNot) and other.canonical == self.canonical
79
def __repr__(self):
80
return self.rep
81
def __hash__(self):
82
return hash(self.canonical)
83
84
# This is our list
85
singmaster_list = [''] + [SingNot(index2singmaster(i+1)) for i in range(48)]; singmaster_list
86
87
class OptimalSolver:
88
"""
89
Interface to Michael Reid's optimal Rubik's Cube solver.
90
"""
91
__cmd = "optimal"
92
93
def __init__(self, verbose=False, wait=True):
94
self.verbose = verbose
95
self.start()
96
if wait:
97
print "Initializing tables..."
98
self.ready()
99
print "Done."
100
101
def start(self):
102
child = pexpect.spawn(self.__cmd)
103
cleaner.cleaner(child.pid, self.__cmd)
104
child.timeout = None
105
self.child = child
106
self._ready = False
107
108
def stop(self):
109
if child:
110
self.child.sendline(chr(3)) # send ctrl-c
111
self.child.sendline(chr(4)) # send ctrl-d
112
self.child.close(True)
113
self.child = None
114
115
def ready(self):
116
if not self._ready:
117
self.child.expect('enter cube')
118
self._ready = True
119
120
def __call__(self, facets):
121
return self.solve(facets)
122
123
def solve(self, facets):
124
"""
125
The initial startup and precomputation are substantial...
126
127
TODO: Let it keep searching once it found a solution?
128
129
EXAMPLES:
130
sage: from sage.interfaces.rubik import *
131
sage: solver = DikSolver()
132
sage: solver = OptimalSolver() # long time
133
Initializing tables...
134
Done.
135
sage: C = RubiksCube("R U")
136
sage: solver.solve(C.facets())
137
'R U'
138
sage: C = RubiksCube("R U F L B D")
139
sage: solver.solve(C.facets())
140
'R U F L B D'
141
sage: C = RubiksCube("R2 D2")
142
sage: solver.solve(C.facets())
143
'R2 D2'
144
"""
145
self.ready()
146
self.child.sendline(self.format_cube(facets))
147
self.child.expect(r"([LRUDBF'2 ]+)\s+\((\d+)q\*?, (\d+)f\*?\)")
148
self.child.sendline(chr(3)) # send ctrl-c
149
return self.child.match.groups()[0].strip()
150
151
def format_cube(self, facets):
152
L = []
153
optimal_solver_list = [SingNot(x) for x in optimal_solver_tokens]
154
for f in optimal_solver_format.split(" "):
155
ix = facets[singmaster_list.index(SingNot(f))-1]
156
facet = singmaster_list[ix]
157
L.append(optimal_solver_list[optimal_solver_list.index(facet)])
158
return " ".join([str(f) for f in L])
159
160
161
162
move_map = {
163
"LD":"L'",
164
"LU":"L",
165
"RD":"R",
166
"RU":"R'",
167
"FA":"F",
168
"FC":"F'",
169
"BA":"B'",
170
"BC":"B",
171
"UR":"U",
172
"UL":"U'",
173
"DR":"D'",
174
"DL":"D"
175
}
176
177
class CubexSolver:
178
179
__cmd = "cubex"
180
181
def __call__(self, facets):
182
return self.solve(facets)
183
184
def solve(self, facets):
185
"""
186
EXAMPLES:
187
sage: from sage.interfaces.rubik import *
188
sage: C = RubiksCube("R U")
189
sage: CubexSolver().solve(C.facets())
190
'R U'
191
sage: C = RubiksCube("R U F L B D")
192
sage: sol = CubexSolver().solve(C.facets()); sol
193
"U' L' L' U L U' L U D L L D' L' D L' D' L D L' U' L D' L' U L' B' U' L' U B L D L D' U' L' U L B L B' L' U L U' L' F' L' F L' F L F' L' D' L' D D L D' B L B' L B' L B F' L F F B' L F' B D' D' L D B' B' L' D' B U' U' L' B' D' F' F' L D F'"
194
sage: RubiksCube(sol) == C
195
True
196
sage: C = RubiksCube("R2 F'")
197
sage: CubexSolver().solve(C.facets())
198
"R' R' F'"
199
sage: C = RubiksCube().scramble()
200
sage: sol = CubexSolver().solve(C.facets())
201
sage: C == RubiksCube(sol)
202
True
203
"""
204
s = self.format_cube(facets)
205
child = pexpect.spawn(self.__cmd+" "+s)
206
ix = child.expect(['210.*?:', '^5\d+(.*)'])
207
if ix == 0:
208
child.expect(['211', pexpect.EOF])
209
moves = child.before.strip().replace(',','').split(' ')
210
return " ".join([move_map[m] for m in reversed(moves)])
211
else:
212
s = child.after
213
while child.expect(['^5\d+', pexpect.EOF]) == 0:
214
s += child.after
215
raise ValueError, s
216
217
def format_cube(self, facets):
218
colors = sum([[i]*8 for i in range(1,7)], [])
219
facet_colors = [0] * 54
220
for i in range(48):
221
f = facets[i]-1
222
f += (f+4) // 8 # to compensate for the centers
223
facet_colors[f] = colors[i]
224
for i in range(6):
225
facet_colors[i*9+4] = i+1
226
return "".join(str(c) for c in facet_colors)
227
228
229
230
231
class DikSolver:
232
233
__cmd = "dikcube"
234
235
def __call__(self, facets):
236
return self.solve(facets)
237
238
def solve(self, facets, timeout=10, extra_time=2):
239
"""
240
EXAMPLES:
241
sage: from sage.interfaces.rubik import *
242
sage: C = RubiksCube().move("R U")
243
sage: DikSolver().solve(C.facets())
244
'R U'
245
sage: C = RubiksCube().move("R U F L B D")
246
sage: DikSolver().solve(C.facets())
247
'R U F L B D'
248
sage: C = RubiksCube().move("R2 F'")
249
sage: DikSolver().solve(C.facets())
250
"R2 F'"
251
"""
252
cube_str = self.format_cube(facets)
253
child = pexpect.spawn(self.__cmd+" -p")
254
child.expect('Initialization done!')
255
child.sendline(cube_str)
256
257
# We use send(chr(4)) instead of sendeof in this case, since
258
# child.sendoef() when run in the background with the Dik solver
259
# sends a SIGTTOU which suspends the process -- this is very bad.
260
# This is only a temporary workaround, and does not fix the problem
261
# on OS X. The Dik C program itself will need to be fixed.
262
# See trac #1683. (TODO) -- willem jp, wstein, mabshoff
263
child.send(chr(4))
264
#child.sendeof()
265
266
ix = child.expect(['Solution[^\n]*:', pexpect.EOF, pexpect.TIMEOUT], timeout=timeout)
267
if ix == 0:
268
child.expect(['[^\n]+'])
269
sol = child.after.strip()
270
start_time = time.time()
271
while extra_time > time.time() - start_time:
272
ix = child.expect(['Solution[^\n]*:', pexpect.EOF, pexpect.TIMEOUT], timeout=extra_time - int(time.time() - start_time))
273
if ix == 0:
274
child.expect(['[^\n]+'])
275
sol = child.after.strip()
276
else:
277
extra_time = 0
278
# format the string into our notation
279
child.close(True)
280
return ' '.join([self.rot_map[m[0]]+str(4-int(m[1])) for m in reversed(sol.split(' '))]).replace('1', '').replace('3',"'")
281
elif ix == 1:
282
# invalid format
283
child.close(True)
284
raise ValueError, child.before
285
else:
286
child.close(True)
287
raise RuntimeError, "timeout"
288
289
def format_cube(self, facets):
290
colors = sum([[i]*8 for i in range(1,7)], [])
291
facet_colors = [0] * 54
292
for i in range(48):
293
f = self.facet_map.index(facets[i])
294
facet_colors[f] = colors[i]
295
# now do the centers
296
facet_colors[4] = 1
297
facet_colors[49] = 6
298
for i in range(2,6):
299
facet_colors[16+i*3] = i
300
return "".join(str(c) for c in facet_colors)
301
302
facet_map = [ 1, 2, 3, \
303
4, 0, 5, \
304
6, 7, 8, \
305
9, 10, 11, 17, 18, 19, 25, 26, 27, 33, 34, 35, \
306
12, 0, 13, 20, 0, 21, 28, 0, 29, 36, 0, 37, \
307
14, 15, 16, 22, 23, 24, 30, 31, 32, 38, 39, 40, \
308
41, 42, 43, \
309
44, 0, 45, \
310
46, 47, 48, \
311
]
312
313
314
# to compensate for different face naming
315
rot_map = dict(zip("BLURDF", "ULFRBD"))
316
317
318
# facet_map = [
319
# 1, 2, 3,
320
# 4, 6,
321
# 7, 8, 9,
322
# 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
323
# 23, 25, 26, 28, 29, 30, 31, 33,
324
# 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45,
325
# 46, 47, 48,
326
# 49, 51,
327
# 52, 53, 54,
328
# ]
329
330
331