Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

r""" 

Interface to several Rubik's cube solvers. 

 

The first is by Michael Reid, and tries to find an optimal solution given 

the cube's state, and may take a long time. 

See http://www.math.ucf.edu/~reid/Rubik/optimal_solver.html 

 

The second is by Eric Dietz, and uses a standard (?) algorithm to 

solve the cube one level at a time. It is extremely fast, but often 

returns a far from optimal solution. 

See http://wrongway.org/?rubiksource 

 

The third is by Dik Winter and implements Kociemba's algorithm which 

finds reasonable solutions relatively quickly, and if it is kept running 

will eventually find the optimal solution. 

 

 

 

AUTHOR: 

-- Optimal was written by Michael Reid <reid@math.ucf.edu> (2004) 

-- Cubex was written by Eric Dietz <root@wrongway.org> (2003) 

-- Kociemba was written by Dik T. Winter <dik.winter@cwi.nl> (1993) 

-- Initial interface by Robert Bradshaw (2007-08) 

""" 

 

######################################################################## 

# Copyright (C) 2007 Robert Bradshaw <robertwb@math.washington.edu> 

# 

# Distributed under the terms of the GNU General Public License (GPL) 

# 

# The full text of the GPL is available at: 

# 

# http://www.gnu.org/licenses/ 

######################################################################## 

from __future__ import print_function 

from __future__ import absolute_import 

 

import pexpect 

import time 

from . import cleaner 

 

from sage.cpython.string import bytes_to_str 

from sage.groups.perm_gps.cubegroup import index2singmaster 

 

 

 

# Can't seem to find consistency in letter ordering 

# between us and them... These are copied from the source. 

optimal_solver_tokens = ["UF", "UR", "UB", "UL", \ 

"DF", "DR", "DB", "DL", \ 

"FR", "FL", "BR", "BL", \ 

"FU", "RU", "BU", "LU", \ 

"FD", "RD", "BD", "LD", \ 

"RF", "LF", "RB", "LB", \ 

"UFR", "URB", "UBL", "ULF", \ 

"DRF", "DFL", "DLB", "DBR", \ 

"FRU", "RBU", "BLU", "LFU", \ 

"RFD", "FLD", "LBD", "BRD", \ 

"RUF", "BUR", "LUB", "FUL", \ 

"FDR", "LDF", "BDL", "RDB"] 

 

# The input format. 

optimal_solver_format = "UF UR UB UL DF DR DB DL FR FL BR BL UFR URB UBL ULF DRF DFL DLB DBR" 

 

class SingNot: 

""" 

This class is to resolve difference between various Singmaster notation. 

Case is ignored, and the second and third letters may be swapped. 

 

EXAMPLES:: 

 

sage: from sage.interfaces.rubik import SingNot 

sage: SingNot("acb") == SingNot("ACB") 

True 

sage: SingNot("acb") == SingNot("bca") 

False 

""" 

def __init__(self, s): 

self.rep = s 

self.canonical = (s[0] + "".join(sorted(list(s[1:])))).lower() 

def __eq__(self, other): 

return isinstance(other, SingNot) and other.canonical == self.canonical 

def __repr__(self): 

return self.rep 

def __hash__(self): 

return hash(self.canonical) 

 

# This is our list 

singmaster_list = [''] + [SingNot(index2singmaster(i+1)) for i in range(48)]; singmaster_list 

 

class OptimalSolver: 

""" 

Interface to Michael Reid's optimal Rubik's Cube solver. 

""" 

__cmd = "optimal" 

 

def __init__(self, verbose=False, wait=True): 

self.verbose = verbose 

self.start() 

if wait: 

print("Initializing tables...") 

self.ready() 

print("Done.") 

 

def start(self): 

child = pexpect.spawn(self.__cmd) 

cleaner.cleaner(child.pid, self.__cmd) 

child.timeout = None 

self.child = child 

self._ready = False 

 

def stop(self): 

if self.child: 

self.child.sendline(chr(3)) # send ctrl-c 

self.child.sendline(chr(4)) # send ctrl-d 

self.child.close(True) 

self.child = None 

 

def ready(self): 

if not self._ready: 

self.child.expect('enter cube') 

self._ready = True 

 

def __call__(self, facets): 

return self.solve(facets) 

 

def solve(self, facets): 

""" 

The initial startup and precomputation are substantial... 

 

TODO: Let it keep searching once it found a solution? 

 

EXAMPLES:: 

 

sage: from sage.interfaces.rubik import * 

sage: solver = DikSolver() 

sage: solver = OptimalSolver() # long time (28s on sage.math, 2012) 

Initializing tables... 

Done. 

sage: C = RubiksCube("R U") 

sage: solver.solve(C.facets()) 

'R U' 

sage: C = RubiksCube("R U F L B D") 

sage: solver.solve(C.facets()) 

'R U F L B D' 

sage: C = RubiksCube("R2 D2") 

sage: solver.solve(C.facets()) 

'R2 D2' 

""" 

self.ready() 

self.child.sendline(self.format_cube(facets)) 

self.child.expect(r"([LRUDBF'2 ]+)\s+\((\d+)q\*?, (\d+)f\*?\)") 

self.child.sendline(chr(3)) # send ctrl-c 

return self.child.match.groups()[0].strip() 

 

def format_cube(self, facets): 

L = [] 

optimal_solver_list = [SingNot(x) for x in optimal_solver_tokens] 

for f in optimal_solver_format.split(" "): 

ix = facets[singmaster_list.index(SingNot(f))-1] 

facet = singmaster_list[ix] 

L.append(optimal_solver_list[optimal_solver_list.index(facet)]) 

return " ".join([str(f) for f in L]) 

 

 

 

move_map = { 

"LD":"L'", 

"LU":"L", 

"RD":"R", 

"RU":"R'", 

"FA":"F", 

"FC":"F'", 

"BA":"B'", 

"BC":"B", 

"UR":"U", 

"UL":"U'", 

"DR":"D'", 

"DL":"D" 

} 

 

class CubexSolver: 

 

__cmd = "cubex" 

 

def __call__(self, facets): 

return self.solve(facets) 

 

def solve(self, facets): 

""" 

EXAMPLES:: 

 

sage: from sage.interfaces.rubik import * 

sage: C = RubiksCube("R U") 

sage: CubexSolver().solve(C.facets()) 

'R U' 

sage: C = RubiksCube("R U F L B D") 

sage: sol = CubexSolver().solve(C.facets()); sol 

"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'" 

sage: RubiksCube(sol) == C 

True 

sage: C = RubiksCube("R2 F'") 

sage: CubexSolver().solve(C.facets()) 

"R' R' F'" 

sage: C = RubiksCube().scramble() 

sage: sol = CubexSolver().solve(C.facets()) 

sage: C == RubiksCube(sol) 

True 

""" 

s = self.format_cube(facets) 

child = pexpect.spawn(self.__cmd+" "+s) 

ix = child.expect(['210.*?:', '^5\d+(.*)']) 

if ix == 0: 

child.expect(['211', pexpect.EOF]) 

moves = bytes_to_str(child.before).strip().replace(',', '').split(' ') 

return " ".join([move_map[m] for m in reversed(moves)]) 

else: 

s = child.after 

while child.expect(['^5\d+', pexpect.EOF]) == 0: 

s += child.after 

raise ValueError(bytes_to_str(s)) 

 

def format_cube(self, facets): 

colors = sum([[i]*8 for i in range(1,7)], []) 

facet_colors = [0] * 54 

for i in range(48): 

f = facets[i]-1 

f += (f+4) // 8 # to compensate for the centers 

facet_colors[f] = colors[i] 

for i in range(6): 

facet_colors[i*9+4] = i+1 

return "".join(str(c) for c in facet_colors) 

 

 

 

 

class DikSolver: 

 

__cmd = "dikcube" 

 

def __call__(self, facets): 

return self.solve(facets) 

 

def solve(self, facets, timeout=10, extra_time=2): 

""" 

EXAMPLES:: 

 

sage: from sage.interfaces.rubik import * 

sage: C = RubiksCube().move("R U") 

sage: DikSolver().solve(C.facets()) 

'R U' 

sage: C = RubiksCube().move("R U F L B D") 

sage: DikSolver().solve(C.facets()) 

'R U F L B D' 

sage: C = RubiksCube().move("R2 F'") 

sage: DikSolver().solve(C.facets()) 

"R2 F'" 

""" 

cube_str = self.format_cube(facets) 

child = pexpect.spawn(self.__cmd+" -p") 

child.expect('Initialization done!') 

child.sendline(cube_str) 

 

# We use send(chr(4)) instead of sendeof in this case, since 

# child.sendoef() when run in the background with the Dik solver 

# sends a SIGTTOU which suspends the process -- this is very bad. 

# This is only a temporary workaround, and does not fix the problem 

# on OS X. The Dik C program itself will need to be fixed. 

# See trac #1683. (TODO) -- willem jp, wstein, mabshoff 

child.send(chr(4)) 

#child.sendeof() 

 

ix = child.expect(['Solution[^\n]*:', pexpect.EOF, pexpect.TIMEOUT], timeout=timeout) 

if ix == 0: 

child.expect(['[^\n]+']) 

sol = child.after.strip() 

start_time = time.time() 

while extra_time > time.time() - start_time: 

ix = child.expect(['Solution[^\n]*:', pexpect.EOF, pexpect.TIMEOUT], timeout=extra_time - int(time.time() - start_time)) 

if ix == 0: 

child.expect(['[^\n]+']) 

sol = child.after.strip() 

else: 

extra_time = 0 

# format the string into our notation 

child.close(True) 

sol = bytes_to_str(sol) 

return ' '.join([self.rot_map[m[0]]+str(4-int(m[1])) for m in reversed(sol.split(' '))]).replace('1', '').replace('3',"'") 

elif ix == 1: 

# invalid format 

child.close(True) 

raise ValueError(bytes_to_str(child.before)) 

else: 

child.close(True) 

raise RuntimeError("timeout") 

 

def format_cube(self, facets): 

colors = sum([[i]*8 for i in range(1,7)], []) 

facet_colors = [0] * 54 

for i in range(48): 

f = self.facet_map.index(facets[i]) 

facet_colors[f] = colors[i] 

# now do the centers 

facet_colors[4] = 1 

facet_colors[49] = 6 

for i in range(2,6): 

facet_colors[16+i*3] = i 

return "".join(str(c) for c in facet_colors) 

 

facet_map = [ 1, 2, 3, \ 

4, 0, 5, \ 

6, 7, 8, \ 

9, 10, 11, 17, 18, 19, 25, 26, 27, 33, 34, 35, \ 

12, 0, 13, 20, 0, 21, 28, 0, 29, 36, 0, 37, \ 

14, 15, 16, 22, 23, 24, 30, 31, 32, 38, 39, 40, \ 

41, 42, 43, \ 

44, 0, 45, \ 

46, 47, 48, \ 

] 

 

 

# to compensate for different face naming 

rot_map = dict(zip("BLURDF", "ULFRBD")) 

 

 

# facet_map = [ 

# 1, 2, 3, 

# 4, 6, 

# 7, 8, 9, 

# 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 

# 23, 25, 26, 28, 29, 30, 31, 33, 

# 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 

# 46, 47, 48, 

# 49, 51, 

# 52, 53, 54, 

# ]