Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/groups/perm_gps/cubegroup.py
8815 views
1
r"""
2
Rubik's cube group functions
3
4
.. _sec-rubik:
5
6
.. NOTE::
7
8
"Rubiks cube" is trademarked. We shall omit the trademark
9
symbol below for simplicity.
10
11
NOTATION:
12
13
`B` denotes a clockwise quarter turn of the back face, `D`
14
denotes a clockwise quarter turn of the down face, and similarly for
15
`F` (front), `L` (left), `R` (right), and `U` (up). Products of moves are read
16
right to left, so for example, `R \cdot U` means move `U` first and then `R`.
17
18
See ``CubeGroup.parse()`` for all possible input notations.
19
20
The "Singmaster notation":
21
22
- moves: `U, D, R, L, F, B` as in the
23
diagram below,
24
25
- corners: `xyz` means the facet is on face `x` (in `R,F,L,U,D,B`) and the
26
clockwise rotation of the corner sends `x-y-z`
27
28
- edges: `xy` means the facet is on face `x` and a flip of the edge sends
29
`x-y`.
30
31
::
32
33
sage: rubik = CubeGroup()
34
sage: rubik.display2d("")
35
+--------------+
36
| 1 2 3 |
37
| 4 top 5 |
38
| 6 7 8 |
39
+------------+--------------+-------------+------------+
40
| 9 10 11 | 17 18 19 | 25 26 27 | 33 34 35 |
41
| 12 left 13 | 20 front 21 | 28 right 29 | 36 rear 37 |
42
| 14 15 16 | 22 23 24 | 30 31 32 | 38 39 40 |
43
+------------+--------------+-------------+------------+
44
| 41 42 43 |
45
| 44 bottom 45 |
46
| 46 47 48 |
47
+--------------+
48
49
AUTHORS:
50
51
- David Joyner (2006-10-21): first version
52
53
- David Joyner (2007-05): changed faces, added legal and solve
54
55
- David Joyner(2007-06): added plotting functions
56
57
- David Joyner (2007, 2008): colors corrected, "solve" rewritten
58
(again),typos fixed.
59
60
- Robert Miller (2007, 2008): editing, cleaned up display2d
61
62
- Robert Bradshaw (2007, 2008): RubiksCube object, 3d
63
plotting.
64
65
- David Joyner (2007-09): rewrote docstring for CubeGroup's
66
"solve".
67
68
- Robert Bradshaw (2007-09): Versatile parse function for
69
all input types.
70
71
- Robert Bradshaw (2007-11): Cleanup.
72
73
REFERENCES:
74
75
- Cameron, P., Permutation Groups. New York: Cambridge
76
University Press, 1999.
77
78
- Wielandt, H., Finite Permutation Groups.
79
New York: Academic Press, 1964.
80
81
- Dixon, J. and Mortimer, B.,
82
Permutation Groups, Springer-Verlag, Berlin/New York, 1996.
83
84
- Joyner,D., Adventures in Group Theory, Johns Hopkins Univ Press,
85
2002.
86
"""
87
88
#**************************************************************************************
89
# Copyright (C) 2006 David Joyner <[email protected]>
90
#
91
# Distributed under the terms of the GNU General Public License (GPL)
92
# http://www.gnu.org/licenses/
93
#**************************************************************************************
94
95
from sage.groups.perm_gps.permgroup import PermutationGroup, PermutationGroup_generic
96
import random
97
98
from sage.structure.sage_object import SageObject
99
100
from sage.rings.all import RationalField, Integer, RDF
101
#from sage.matrix.all import MatrixSpace
102
from sage.interfaces.all import gap
103
from sage.groups.perm_gps.permgroup_element import PermutationGroupElement
104
from sage.plot.polygon import polygon
105
from sage.plot.text import text
106
pi = RDF.pi()
107
108
109
from sage.plot.plot3d.shapes import Box
110
from sage.plot.plot3d.texture import Texture
111
112
####################### predefined colors ##################
113
114
named_colors = {
115
'red': (1,0,0), ## F face
116
'green': (0,1,0), ## R face
117
'blue': (0,0,1), ## D face
118
'yellow': (1,1,0), ## L face
119
'white': (1,1,1), ## none
120
'orange': (1,0.6,0.3), ## B face
121
'purple': (1,0,1), ## none
122
'lpurple': (1,0.63,1), ## U face
123
'lightblue': (0,1,1), ## none
124
'lgrey': (0.75,0.75,0.75), ## sagemath.org color
125
}
126
globals().update(named_colors)
127
128
#########################################################
129
#written by Tom Boothby, placed in the public domain
130
131
def xproj(x,y,z,r):
132
r"""
133
Return the `x`-projection of `(x,y,z)` rotated by `r`.
134
135
EXAMPLES::
136
137
sage: from sage.groups.perm_gps.cubegroup import rotation_list, xproj
138
sage: rot = rotation_list(30, 45)
139
sage: xproj(1,2,3,rot)
140
0.612372435696
141
"""
142
return (y*r[1] - x*r[3])*r[2]
143
144
def yproj(x,y,z,r):
145
r"""
146
Return the `y`-projection of `(x,y,z)` rotated by `r`.
147
148
EXAMPLES::
149
150
sage: from sage.groups.perm_gps.cubegroup import rotation_list, yproj
151
sage: rot = rotation_list(30, 45)
152
sage: yproj(1,2,3,rot)
153
1.37849741698
154
"""
155
return z*r[2] - (x*r[1] + y*r[2])*r[0]
156
157
def rotation_list(tilt,turn):
158
r"""
159
Return a list `[\sin(\theta), \sin(\phi), \cos(\theta), \cos(\phi)]` of
160
rotations where `\theta` is ``tilt`` and `\phi` is ``turn``.
161
162
EXAMPLES::
163
164
sage: from sage.groups.perm_gps.cubegroup import rotation_list
165
sage: rotation_list(30, 45)
166
[0.5, 0.707106781187, 0.866025403784, 0.707106781187]
167
"""
168
from sage.functions.all import sin, cos
169
return [ sin(tilt*pi/180.0), sin(turn*pi/180.0), cos(tilt*pi/180.0), cos(turn*pi/180.0) ]
170
171
def polygon_plot3d(points, tilt=30, turn=30, **kwargs):
172
r"""
173
Plot a polygon viewed from an angle determined by ``tilt``, ``turn``, and
174
vertices ``points``.
175
176
.. WARNING::
177
178
The ordering of the points is important to get "correct"
179
and if you add several of these plots together, the one added first
180
is also drawn first (ie, addition of Graphics objects is not
181
commutative).
182
183
The following example produced a green-colored square with vertices
184
at the points indicated.
185
186
EXAMPLES::
187
188
sage: from sage.groups.perm_gps.cubegroup import polygon_plot3d,green
189
sage: P = polygon_plot3d([[1,3,1],[2,3,1],[2,3,2],[1,3,2],[1,3,1]],rgbcolor=green)
190
"""
191
rot = rotation_list(tilt,turn)
192
points2 = [(xproj(x,y,z,rot), yproj(x,y,z,rot)) for (x,y,z) in points ]
193
return polygon(points2, **kwargs)
194
195
###########################################################
196
197
############# lots of "internal" utility plot functions #########
198
199
200
def inv_list(lst):
201
r"""
202
Input a list of ints `1, \ldots, m` (in any order), outputs inverse
203
perm.
204
205
EXAMPLES::
206
207
sage: from sage.groups.perm_gps.cubegroup import inv_list
208
sage: L = [2,3,1]
209
sage: inv_list(L)
210
[3, 1, 2]
211
"""
212
return [lst.index(i)+1 for i in range(1,1+len(lst))]
213
214
face_polys = {
215
### bottom layer L, F, R, B
216
'ldb': [[-3,0],[-2,0], [-2,1], [-3,1]], #square labeled 14
217
'ld': [[-2,0],[-1,0], [-1,1], [-2,1]], #square labeled 15
218
'lfd': [[-1,0],[0,0], [0,1], [-1,1]], #square labeled 16
219
'fdl': [[0,0],[1,0], [1,1], [0,1]], #square labeled 22
220
'fd': [[1,0],[2,0], [2,1], [1,1]], #square labeled 23
221
'frd': [[2,0],[3,0], [3,1], [2,1]], #square labeled 24
222
'rdf': [[3,0],[4,0], [4,1], [3,1]], #square labeled 30
223
'rd': [[4,0],[5,0], [5,1], [4,1]], #square labeled 31
224
'rbd': [[5,0],[6,0], [6,1], [5,1]], #square labeled 32
225
'bdr': [[6,0],[7,0], [7,1], [6,1]], #square labeled 38
226
'bd': [[7,0],[8,0], [8,1], [7,1]], #square labeled 39
227
'bld': [[8,0],[9,0], [9,1], [8,1]], #square labeled 40
228
### middle layer L,F,R, B
229
'lb': [[-3,1],[-2,1], [-2,2], [-3,2]], #square labeled 12
230
'l_center': [[-2,1],[-1,1], [-1,2], [-2,2]], #center square
231
'lf': [[-1,1],[0,1], [0,2], [-1,2]], #square labeled 13
232
'fl': [[0,1],[1,1], [1,2], [0,2]], #square labeled 20
233
'f_center': [[1,1],[2,1], [2,2], [1,2]], #center square
234
'fr': [[2,1],[3,1], [3,2], [2,2]], #square labeled 21
235
'rf': [[3,1],[4,1], [4,2], [3,2]], #square labeled 28
236
'r_center': [[4,1],[5,1], [5,2], [4,2]], #center square
237
'rb': [[5,1],[6,1], [6,2], [5,2]], #square labeled 29
238
'br': [[6,1],[7,1], [7,2], [6,2]], #square labeled 36
239
'b_center': [[7,1],[8,1], [8,2], [7,2]], #center square
240
'bl': [[8,1],[9,1], [9,2], [8,2]], #square labeled 37
241
## top layer L, F, R, B
242
'lbu': [[-3,2],[-2,2], [-2,3], [-3,3]], #square labeled 9
243
'lu': [[-2,2],[-1,2], [-1,3], [-2,3]], #square labeled 10
244
'luf': [[-1,2],[0,2], [0,3], [-1,3]], #square labeled 11
245
'flu': [[0,2],[1,2], [1,3], [0,3]], #square labeled 17
246
'fu': [[1,2],[2,2], [2,3], [1,3]], #square labeled 18
247
'fur': [[2,2],[3,2], [3,3], [2,3]], #square labeled 19
248
'ruf': [[3,2],[4,2], [4,3], [3,3]], #square labeled 25
249
'ru': [[4,2],[5,2], [5,3], [4,3]], #square labeled 26
250
'rub': [[5,2],[6,2], [6,3], [5,3]], #square labeled 27
251
'bur': [[6,2],[7,2], [7,3], [6,3]], #square labeled 33
252
'bu': [[7,2],[8,2], [8,3], [7,3]], #square labeled 34
253
'bul': [[8,2],[9,2], [9,3], [8,3]], #square labeled 35
254
# down face
255
'dlf': [[0,-1],[1,-1], [1,0], [0,0]], #square labeled 41
256
'df': [[1,-1],[2,-1], [2,0], [1,0]], #square labeled 42
257
'dfr': [[2,-1],[3,-1], [3,0], [2,0]], #square labeled 43
258
'dl': [[0,-2],[1,-2], [1,-1], [0,-1]], #square labeled 44
259
'd_center': [[1,-2],[2,-2], [2,-1], [1,-1]], #center square
260
'dr': [[2,-2],[3,-2], [3,-1], [2,-1]], #square labeled 45
261
'dlb': [[0,-3],[1,-3], [1,-2], [0,-2]], #square labeled 46
262
'db': [[1,-3],[2,-3], [2,-2], [1,-2]], #square labeled 47
263
'drb': [[2,-3],[3,-3], [3,-2], [2,-2]], #square labeled 48
264
# up face
265
'ufl': [[0,3],[1,3], [1,4], [0,4]], #square labeled 6
266
'uf': [[1,3],[2,3], [2,4], [1,4]], #square labeled 7
267
'urf': [[2,3],[3,3], [3,4], [2,4]], #square labeled 8
268
'ul': [[0,4],[1,4], [1,5], [0,5]], #square labeled 4
269
'u_center': [[1,4],[2,4], [2,5], [1,5]], #center square
270
'ur': [[2,4],[3,4], [3,5], [2,5]], #square labeled 5
271
'ulb': [[0,6],[1,6], [1,5], [0,5]], #square labeled 1
272
'ub': [[1,6],[2,6], [2,5], [1,5]], #square labeled 2
273
'ubr': [[2,6],[3,6], [3,5], [2,5]], #square labeled 3
274
}
275
276
def create_poly(face, color):
277
"""
278
Create the polygon given by ``face`` with color ``color``.
279
280
EXAMPLES::
281
282
sage: from sage.groups.perm_gps.cubegroup import create_poly, red
283
sage: create_poly('ur', red)
284
"""
285
return polygon(face_polys[face], rgbcolor=color)
286
287
####################################################
288
289
singmaster_indices = {
290
1: "ulb",
291
2: "ub",
292
3: "ubr",
293
4: "ul",
294
5: "ur",
295
6: "ufl",
296
7: "uf",
297
8: "urf",
298
14: "ldb",
299
15: "ld",
300
16: "lfd",
301
12: "lb",
302
13: "lf",
303
9: "lbu",
304
10: "lu",
305
11: "luf",
306
17: "flu",
307
18: "fu",
308
19: "fur",
309
20: "fl",
310
21: "fr",
311
22: "fdl",
312
23: "fd",
313
24: "frd",
314
41: "dlf",
315
42: "df",
316
43: "dfr",
317
44: "dl",
318
45: "dr",
319
46: "dlb",
320
47: "db",
321
48: "drb",
322
33: "bur",
323
34: "bu",
324
35: "bul",
325
36: "br",
326
37: "bl",
327
38: "bdr",
328
39: "bd",
329
40: "bld",
330
25: "ruf",
331
26: "ru",
332
27: "rub",
333
28: "rf",
334
29: "rb",
335
30: "rdf",
336
31: "rd",
337
32: "rbd",
338
}
339
340
def index2singmaster(facet):
341
"""
342
Translate index used (eg, 43) to Singmaster facet notation (eg,
343
fdr).
344
345
EXAMPLES::
346
347
sage: from sage.groups.perm_gps.cubegroup import *
348
sage: index2singmaster(41)
349
'dlf'
350
"""
351
return singmaster_indices[facet]
352
353
def color_of_square(facet, colors=['lpurple', 'yellow', 'red', 'green', 'orange', 'blue']):
354
"""
355
Return the color the facet has in the solved state.
356
357
EXAMPLES::
358
359
sage: from sage.groups.perm_gps.cubegroup import *
360
sage: color_of_square(41)
361
'blue'
362
"""
363
return colors[(facet-1) // 8]
364
365
cubie_center_list = {
366
# centers of the cubies on the F,U, R faces
367
1: [1/2,1/2,5/2], # ulb
368
2: [1/2,3/2,5/2], # ub
369
3: [1/2,5/2,5/2], # ubr
370
4: [3/2,1/2,5/2], # ul
371
5: [3/2,5/2,5/2], # ur
372
6: [5/2,1/2,5/2], # ufl
373
7: [5/2,3/2,5/2], # uf
374
8: [5/2,5/2,5/2], # urf
375
17: [5/2,1/2,5/2], # flu
376
18: [5/2,3/2,5/2], # fu
377
19: [5/2,5/2,5/2], # fur
378
20: [5/2,1/2,3/2], # fl
379
21: [5/2,5/2,3/2], # fr
380
22: [5/2,1/2,1/2], # fdl
381
23: [5/2,3/2,1/2], # fd
382
24: [5/2,5/2,1/2], # frd
383
25: [5/2,5/2,5/2], # rfu
384
26: [3/2,5/2,5/2], # ru
385
27: [1/2,5/2,5/2], # rub
386
28: [5/2,5/2,3/2], # rf
387
29: [1/2,5/2,3/2], # rb
388
30: [5/2,5/2,1/2], # rdf
389
31: [3/2,5/2,1/2], # rd
390
32: [1/2,5/2,1/2], # rbd
391
}
392
393
def cubie_centers(label):
394
r"""
395
Return the cubie center list element given by ``label``.
396
397
EXAMPLES::
398
399
sage: from sage.groups.perm_gps.cubegroup import cubie_centers
400
sage: cubie_centers(3)
401
[0, 2, 2]
402
"""
403
return cubie_center_list[label]
404
405
def cubie_colors(label,state0):
406
r"""
407
Return the color of the cubie given by ``label`` at ``state0``.
408
409
EXAMPLES::
410
411
sage: from sage.groups.perm_gps.cubegroup import cubie_colors
412
sage: G = CubeGroup()
413
sage: g = G.parse("R*U")
414
sage: cubie_colors(3, G.facets(g))
415
[(1, 1, 1), (1, 0.63, 1), (1, 0.6, 0.3)]
416
"""
417
# colors of the cubies on the F,U, R faces
418
clr_any = named_colors['white']
419
state = inv_list(state0)
420
if label == 1: return [clr_any, named_colors[color_of_square(state[1-1])], clr_any] #ulb,
421
if label == 2: return [clr_any,named_colors[color_of_square(state[2-1])],clr_any] # ub,
422
if label == 3: return [clr_any, named_colors[color_of_square(state[3-1])], named_colors[color_of_square(state[27-1])]] # ubr,
423
if label == 4: return [clr_any, named_colors[color_of_square(state[4-1])], clr_any] # ul,
424
if label == 5: return [clr_any, named_colors[color_of_square(state[5-1])], named_colors[color_of_square(state[26-1])]] # ur,
425
if label == 6: return [named_colors[color_of_square(state[17-1])], named_colors[color_of_square(state[6-1])], clr_any] # ufl,
426
if label == 7: return [named_colors[color_of_square(state[18-1])], named_colors[color_of_square(state[7-1])], clr_any] # uf,
427
if label == 8: return [named_colors[color_of_square(state[19-1])], named_colors[color_of_square(state[8-1])], named_colors[color_of_square(state[25-1])]] # urf,
428
if label == 17: return [named_colors[color_of_square(state[17-1])], named_colors[color_of_square(state[6-1])], clr_any] # flu
429
if label == 18: return [named_colors[color_of_square(state[18-1])], named_colors[color_of_square(state[7-1])], clr_any] # fu
430
if label == 19: return [named_colors[color_of_square(state[19-1])], named_colors[color_of_square(state[8-1])], named_colors[color_of_square(state[25-1])]] # fur
431
if label == 20: return [named_colors[color_of_square(state[20-1])], clr_any, clr_any] # fl
432
if label == 21: return [named_colors[color_of_square(state[21-1])], clr_any, named_colors[color_of_square(state[28-1])]] # fr
433
if label == 22: return [named_colors[color_of_square(state[22-1])], clr_any, clr_any] # fdl
434
if label == 23: return [named_colors[color_of_square(state[23-1])], clr_any, clr_any] # fd
435
if label == 24: return [named_colors[color_of_square(state[24-1])], clr_any, named_colors[color_of_square(state[30-1])]] # frd
436
if label == 25: return [named_colors[color_of_square(state[19-1])],named_colors[color_of_square(state[8-1])],named_colors[color_of_square(state[25-1])]] #rfu,
437
if label == 26: return [clr_any,named_colors[color_of_square(state[5-1])],named_colors[color_of_square(state[26-1])]] # ru,
438
if label == 27: return [clr_any,named_colors[color_of_square(state[3-1])],named_colors[color_of_square(state[27-1])]] # rub,
439
if label == 28: return [named_colors[color_of_square(state[21-1])],clr_any,named_colors[color_of_square(state[28-1])]] # rf,
440
if label == 29: return [clr_any,clr_any,named_colors[color_of_square(state[29-1])]] # rb,
441
if label == 30: return [named_colors[color_of_square(state[24-1])],clr_any,named_colors[color_of_square(state[30-1])]] # rdf,
442
if label == 31: return [clr_any,clr_any,named_colors[color_of_square(state[31-1])]] # rd,
443
if label == 32: return [clr_any,clr_any,named_colors[color_of_square(state[32-1])]] #rbd,
444
445
def plot3d_cubie(cnt, clrs):
446
r"""
447
Plot the front, up and right face of a cubie centered at cnt and
448
rgbcolors given by clrs (in the order FUR).
449
450
Type ``P.show()`` to view.
451
452
EXAMPLES::
453
454
sage: from sage.groups.perm_gps.cubegroup import *
455
sage: clrF = blue; clrU = red; clrR = green
456
sage: P = plot3d_cubie([1/2,1/2,1/2],[clrF,clrU,clrR])
457
"""
458
x = cnt[0]-1/2; y = cnt[1]-1/2; z = cnt[2]-1/2
459
#ptsD = [[x+0,y+0,0+z],[x+1,y+0,0+z],[x+1,y+1,0+z],[x+0,y+1,0+z],[x+0,y+0,0+z]]
460
ptsF = [[x+1,y+0,0+z],[x+1,y+1,0+z],[x+1,y+1,1+z],[x+1,y+0,1+z],[x+1,y+0,0+z]]
461
#ptsB = [[x+0,y+0,0+z],[x+0,y+1,0+z],[x+0,y+1,1+z],[x+0,y+0,1+z],[x+0,y+0,0+z]]
462
ptsU = [[x+0,y+0,1+z],[x+1,y+0,1+z],[x+1,y+1,1+z],[x+0,y+1,1+z],[x+0,y+0,1+z]]
463
#ptsL = [[x+0,y+0,0+z],[x+1,y+0,0+z],[x+1,y+0,1+z],[x+0,y+0,1+z],[x+0,y+0,0+z]]
464
ptsR = [[x+0,y+1,0+z],[x+1,y+1,0+z],[x+1,y+1,1+z],[x+0,y+1,1+z],[x+0,y+1,0+z]]
465
PR = polygon_plot3d(ptsR,rgbcolor=clrs[2])
466
PU = polygon_plot3d(ptsU,rgbcolor=clrs[1])
467
PF = polygon_plot3d(ptsF,rgbcolor=clrs[0])
468
P = PR+PF+PU
469
P.axes(show=False)
470
return P
471
472
473
####################### end of "internal" utility plot functions #################
474
475
476
class CubeGroup(PermutationGroup_generic):
477
r"""
478
A python class to help compute Rubik's cube group actions.
479
480
.. NOTE::
481
482
This group is also available via ``groups.permutation.RubiksCube()``.
483
484
EXAMPLES:
485
486
If G denotes the cube group then it may be regarded as a
487
subgroup of ``SymmetricGroup(48)``, where the 48 facets are labeled as
488
follows.
489
490
::
491
492
sage: rubik = CubeGroup()
493
sage: rubik.display2d("")
494
+--------------+
495
| 1 2 3 |
496
| 4 top 5 |
497
| 6 7 8 |
498
+------------+--------------+-------------+------------+
499
| 9 10 11 | 17 18 19 | 25 26 27 | 33 34 35 |
500
| 12 left 13 | 20 front 21 | 28 right 29 | 36 rear 37 |
501
| 14 15 16 | 22 23 24 | 30 31 32 | 38 39 40 |
502
+------------+--------------+-------------+------------+
503
| 41 42 43 |
504
| 44 bottom 45 |
505
| 46 47 48 |
506
+--------------+
507
508
::
509
510
sage: rubik
511
The Rubik's cube group with generators R,L,F,B,U,D in SymmetricGroup(48).
512
513
TESTS::
514
515
sage: groups.permutation.RubiksCube()
516
The Rubik's cube group with generators R,L,F,B,U,D in SymmetricGroup(48).
517
"""
518
def __init__(self):
519
"""
520
Initialize ``self``.
521
522
EXAMPLES::
523
524
sage: rubik = CubeGroup()
525
sage: TestSuite(rubik).run(skip="_test_enumerated_set_contains") # because the group is very large
526
527
TESTS:
528
529
Check that :trac:`11360` is fixed::
530
531
sage: rubik = CubeGroup()
532
sage: rubik.order()
533
43252003274489856000
534
"""
535
U = "( 1, 3, 8, 6)( 2, 5, 7, 4)( 9,33,25,17)(10,34,26,18)(11,35,27,19)" ## U = top
536
L = "( 9,11,16,14)(10,13,15,12)( 1,17,41,40)( 4,20,44,37)( 6,22,46,35)" ## L = left
537
F = "(17,19,24,22)(18,21,23,20)( 6,25,43,16)( 7,28,42,13)( 8,30,41,11)" ## F = front
538
R = "(25,27,32,30)(26,29,31,28)( 3,38,43,19)( 5,36,45,21)( 8,33,48,24)" ## R = right
539
B = "(33,35,40,38)(34,37,39,36)( 3, 9,46,32)( 2,12,47,29)( 1,14,48,27)" ## B = back or rear
540
D = "(41,43,48,46)(42,45,47,44)(14,22,30,38)(15,23,31,39)(16,24,32,40)" ## D = down or bottom
541
PermutationGroup_generic.__init__(self, gens=[B,D,F,L,R,U], canonicalize=False)
542
543
def gen_names(self):
544
"""
545
Return the names of the generators.
546
547
EXAMPLES::
548
549
sage: rubik = CubeGroup()
550
sage: rubik.gen_names()
551
['B', 'D', 'F', 'L', 'R', 'U']
552
"""
553
return ['B','D','F','L','R','U']
554
555
def __repr__(self):
556
"""
557
Return a string representation of ``self``.
558
559
EXAMPLES::
560
561
sage: rubik = CubeGroup()
562
sage: rubik
563
The Rubik's cube group with generators R,L,F,B,U,D in SymmetricGroup(48).
564
"""
565
return "The Rubik's cube group with generators R,L,F,B,U,D in SymmetricGroup(48)."
566
567
def group(self):
568
r"""
569
This is deprecated in trac:`11360`. Use the :class:`CubeGroup` instead.
570
571
EXAMPLES::
572
573
sage: CubeGroup().group()
574
doctest:...: DeprecationWarning: group() is deprecated. Use the CubeGroup instead.
575
See http://trac.sagemath.org/11360 for details.
576
The Rubik's cube group with generators R,L,F,B,U,D in SymmetricGroup(48).
577
"""
578
from sage.misc.superseded import deprecation
579
deprecation(11360, 'group() is deprecated. Use the CubeGroup instead.')
580
return self
581
582
def B(self):
583
"""
584
Return the generator `B` in Singmaster notation.
585
586
EXAMPLES::
587
588
sage: rubik = CubeGroup()
589
sage: rubik.B()
590
(1,14,48,27)(2,12,47,29)(3,9,46,32)(33,35,40,38)(34,37,39,36)
591
"""
592
return self.gens()[0]
593
594
def D(self):
595
"""
596
Return the generator `D` in Singmaster notation.
597
598
EXAMPLES::
599
600
sage: rubik = CubeGroup()
601
sage: rubik.D()
602
(14,22,30,38)(15,23,31,39)(16,24,32,40)(41,43,48,46)(42,45,47,44)
603
"""
604
return self.gens()[1]
605
606
def F(self):
607
"""
608
Return the generator `F` in Singmaster notation.
609
610
EXAMPLES::
611
612
sage: rubik = CubeGroup()
613
sage: rubik.F()
614
(6,25,43,16)(7,28,42,13)(8,30,41,11)(17,19,24,22)(18,21,23,20)
615
"""
616
return self.gens()[2]
617
618
def L(self):
619
"""
620
Return the generator `L` in Singmaster notation.
621
622
EXAMPLES::
623
624
sage: rubik = CubeGroup()
625
sage: rubik.L()
626
(1,17,41,40)(4,20,44,37)(6,22,46,35)(9,11,16,14)(10,13,15,12)
627
"""
628
return self.gens()[3]
629
630
def R(self):
631
"""
632
Return the generator `R` in Singmaster notation.
633
634
EXAMPLES::
635
636
sage: rubik = CubeGroup()
637
sage: rubik.R()
638
(3,38,43,19)(5,36,45,21)(8,33,48,24)(25,27,32,30)(26,29,31,28)
639
"""
640
return self.gens()[4]
641
642
def U(self):
643
"""
644
Return the generator `U` in Singmaster notation.
645
646
EXAMPLES::
647
648
sage: rubik = CubeGroup()
649
sage: rubik.U()
650
(1,3,8,6)(2,5,7,4)(9,33,25,17)(10,34,26,18)(11,35,27,19)
651
"""
652
return self.gens()[5]
653
654
def parse(self, mv, check=True):
655
r"""
656
This function allows one to create the permutation group element
657
from a variety of formats.
658
659
INPUT:
660
661
- ``mv`` -- Can one of the following:
662
663
- ``list`` - list of facets (as returned by
664
self.facets())
665
666
- ``dict`` - list of faces (as returned by
667
``self.faces()``)
668
669
- ``str`` - either cycle notation (passed to GAP) or
670
a product of generators or Singmaster notation
671
672
- ``perm_group element`` - returned as an element of ``self``
673
674
- ``check`` -- check if the input is valid
675
676
EXAMPLES::
677
678
sage: C = CubeGroup()
679
sage: C.parse(range(1,49))
680
()
681
sage: g = C.parse("L"); g
682
(1,17,41,40)(4,20,44,37)(6,22,46,35)(9,11,16,14)(10,13,15,12)
683
sage: C.parse(str(g)) == g
684
True
685
sage: facets = C.facets(g); facets
686
[17, 2, 3, 20, 5, 22, 7, 8, 11, 13, 16, 10, 15, 9, 12, 14, 41, 18, 19, 44, 21, 46, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 6, 36, 4, 38, 39, 1, 40, 42, 43, 37, 45, 35, 47, 48]
687
sage: C.parse(facets)
688
(1,17,41,40)(4,20,44,37)(6,22,46,35)(9,11,16,14)(10,13,15,12)
689
sage: C.parse(facets) == g
690
True
691
sage: faces = C.faces("L"); faces
692
{'right': [[25, 26, 27], [28, 0, 29], [30, 31, 32]], 'up': [[17, 2, 3], [20, 0, 5], [22, 7, 8]], 'back': [[33, 34, 6], [36, 0, 4], [38, 39, 1]], 'down': [[40, 42, 43], [37, 0, 45], [35, 47, 48]], 'front': [[41, 18, 19], [44, 0, 21], [46, 23, 24]], 'left': [[11, 13, 16], [10, 0, 15], [9, 12, 14]]}
693
sage: C.parse(faces) == C.parse("L")
694
True
695
sage: C.parse("L' R2") == C.parse("L^(-1)*R^2")
696
True
697
sage: C.parse("L' R2")
698
(1,40,41,17)(3,43)(4,37,44,20)(5,45)(6,35,46,22)(8,48)(9,14,16,11)(10,12,15,13)(19,38)(21,36)(24,33)(25,32)(26,31)(27,30)(28,29)
699
sage: C.parse("L^4")
700
()
701
sage: C.parse("L^(-1)*R")
702
(1,40,41,17)(3,38,43,19)(4,37,44,20)(5,36,45,21)(6,35,46,22)(8,33,48,24)(9,14,16,11)(10,12,15,13)(25,27,32,30)(26,29,31,28)
703
"""
704
if isinstance(mv, PermutationGroupElement):
705
# mv is a perm_group element, return mv
706
return mv if mv.parent() is self else PermutationGroup_generic.__call__(self, mv, check)
707
elif isinstance(mv, str):
708
# It is a string: may be in cycle notation or Rubik's notation
709
if '(' in mv and not '^' in mv:
710
return PermutationGroup_generic.__call__(self, mv, check)
711
else:
712
gens = self.gens()
713
names = self.gen_names()
714
map = {}
715
for i in range(6):
716
map[names[i]] = gens[i]
717
g = self.identity()
718
mv = mv.strip().replace(" ","*").replace("**", "*").replace("'", "-1").replace('^','').replace('(','').replace(')','')
719
M = mv.split("*")
720
for m in M:
721
if len(m) == 0:
722
pass
723
elif len(m) == 1:
724
g *= map[m[0]]
725
else:
726
g *= map[m[0]]**int(m[1:])
727
return g
728
elif isinstance(mv, dict):
729
state = mv
730
state_facets = []
731
keyss = state.keys()
732
keyss.sort()
733
for k in keyss:
734
r = state[k][0]+state[k][1]+state[k][2]
735
r.remove(0)
736
state_facets = state_facets + r
737
state0 = self.faces("")
738
state0_facets = []
739
keyss = state0.keys()
740
keyss.sort()
741
for k in keyss:
742
r = state0[k][0]+state0[k][1]+state0[k][2]
743
r.remove(0)
744
state0_facets = state0_facets + r
745
p1 = [state0_facets.index(x) for x in range(1,49)]
746
p2 = [state_facets[j] for j in p1]
747
return PermutationGroup_generic.__call__(self, p2, check)
748
else:
749
return PermutationGroup_generic.__call__(self, mv, check)
750
751
__call__ = parse
752
753
def facets(self, g=None):
754
r"""
755
Return the set of facets on which the group acts. This function is
756
a "constant".
757
758
EXAMPLES::
759
760
sage: rubik = CubeGroup()
761
sage: rubik.facets() == range(1,49)
762
True
763
"""
764
fcts = range(1,49)
765
if g is not None:
766
return [g(i) for i in fcts]
767
else:
768
return fcts
769
770
def faces(self, mv):
771
r"""
772
Return the dictionary of faces created by the effect of the move
773
mv, which is a string of the form `X^a*Y^b*...`, where
774
`X, Y, \ldots` are in `\{R,L,F,B,U,D\}` and
775
`a, b, \ldots` are integers. We call this ordering of the faces
776
the "BDFLRU, L2R, T2B ordering".
777
778
EXAMPLES::
779
780
sage: rubik = CubeGroup()
781
782
Here is the dictionary of the solved state::
783
784
sage: sorted(rubik.faces("").items())
785
[('back', [[33, 34, 35], [36, 0, 37], [38, 39, 40]]),
786
('down', [[41, 42, 43], [44, 0, 45], [46, 47, 48]]),
787
('front', [[17, 18, 19], [20, 0, 21], [22, 23, 24]]),
788
('left', [[9, 10, 11], [12, 0, 13], [14, 15, 16]]),
789
('right', [[25, 26, 27], [28, 0, 29], [30, 31, 32]]),
790
('up', [[1, 2, 3], [4, 0, 5], [6, 7, 8]])]
791
792
Now the dictionary of the state obtained after making the move `R`
793
followed by `L`::
794
795
sage: sorted(rubik.faces("R*U").items())
796
[('back', [[48, 26, 27], [45, 0, 37], [43, 39, 40]]),
797
('down', [[41, 42, 11], [44, 0, 21], [46, 47, 24]]),
798
('front', [[9, 10, 8], [20, 0, 7], [22, 23, 6]]),
799
('left', [[33, 34, 35], [12, 0, 13], [14, 15, 16]]),
800
('right', [[19, 29, 32], [18, 0, 31], [17, 28, 30]]),
801
('up', [[3, 5, 38], [2, 0, 36], [1, 4, 25]])]
802
"""
803
fcts = self.facets(self.parse(mv))
804
faceR = [[fcts[24],fcts[25],fcts[26]],[fcts[27],0,fcts[28]],[fcts[29],fcts[30],fcts[31]]]
805
faceL = [[fcts[8],fcts[9],fcts[10]],[fcts[11],0,fcts[12]],[fcts[13],fcts[14],fcts[15]]]
806
faceU = [[fcts[0],fcts[1],fcts[2]],[fcts[3],0,fcts[4]],[fcts[5],fcts[6],fcts[7]]]
807
faceD = [[fcts[40],fcts[41],fcts[42]],[fcts[43],0,fcts[44]],[fcts[45],fcts[46],fcts[47]]]
808
faceF = [[fcts[16],fcts[17],fcts[18]],[fcts[19],0,fcts[20]],[fcts[21],fcts[22],fcts[23]]]
809
faceB = [[fcts[32],fcts[33],fcts[34]],[fcts[35],0,fcts[36]],[fcts[37],fcts[38],fcts[39]]]
810
return {'right':faceR,'left':faceL,'up':faceU,'down':faceD,'front':faceF,'back':faceB}
811
812
def move(self, mv):
813
r"""
814
Return the group element and the reordered list of facets, as
815
moved by the list ``mv`` (read left-to-right)
816
817
INPUT:
818
819
- ``mv`` -- A string of the form ``Xa*Yb*...``,
820
where ``X``, ``Y``, ... are in ``R``, ``L``, ``F``, ``B``, ``U``,
821
``D`` and ``a``, ``b``, ... are integers.
822
823
EXAMPLES::
824
825
sage: rubik = CubeGroup()
826
sage: rubik.move("")[0]
827
()
828
sage: rubik.move("R")[0]
829
(3,38,43,19)(5,36,45,21)(8,33,48,24)(25,27,32,30)(26,29,31,28)
830
sage: rubik.R()
831
(3,38,43,19)(5,36,45,21)(8,33,48,24)(25,27,32,30)(26,29,31,28)
832
"""
833
g = self.parse(mv)
834
return g, self.facets(g)
835
836
def display2d(self,mv):
837
r"""
838
Print the 2d representation of ``self``.
839
840
EXAMPLES::
841
842
sage: rubik = CubeGroup()
843
sage: rubik.display2d("R")
844
+--------------+
845
| 1 2 38 |
846
| 4 top 36 |
847
| 6 7 33 |
848
+------------+--------------+-------------+------------+
849
| 9 10 11 | 17 18 3 | 27 29 32 | 48 34 35 |
850
| 12 left 13 | 20 front 5 | 26 right 31 | 45 rear 37 |
851
| 14 15 16 | 22 23 8 | 25 28 30 | 43 39 40 |
852
+------------+--------------+-------------+------------+
853
| 41 42 19 |
854
| 44 bottom 21 |
855
| 46 47 24 |
856
+--------------+
857
"""
858
print self.repr2d(mv)
859
860
def repr2d(self, mv):
861
r"""
862
Displays a 2D map of the Rubik's cube after the move mv has been
863
made. Nothing is returned.
864
865
EXAMPLES::
866
867
sage: rubik = CubeGroup()
868
sage: print rubik.repr2d("")
869
+--------------+
870
| 1 2 3 |
871
| 4 top 5 |
872
| 6 7 8 |
873
+------------+--------------+-------------+------------+
874
| 9 10 11 | 17 18 19 | 25 26 27 | 33 34 35 |
875
| 12 left 13 | 20 front 21 | 28 right 29 | 36 rear 37 |
876
| 14 15 16 | 22 23 24 | 30 31 32 | 38 39 40 |
877
+------------+--------------+-------------+------------+
878
| 41 42 43 |
879
| 44 bottom 45 |
880
| 46 47 48 |
881
+--------------+
882
883
::
884
885
sage: print rubik.repr2d("R")
886
+--------------+
887
| 1 2 38 |
888
| 4 top 36 |
889
| 6 7 33 |
890
+------------+--------------+-------------+------------+
891
| 9 10 11 | 17 18 3 | 27 29 32 | 48 34 35 |
892
| 12 left 13 | 20 front 5 | 26 right 31 | 45 rear 37 |
893
| 14 15 16 | 22 23 8 | 25 28 30 | 43 39 40 |
894
+------------+--------------+-------------+------------+
895
| 41 42 19 |
896
| 44 bottom 21 |
897
| 46 47 24 |
898
+--------------+
899
900
You can see the right face has been rotated but not the left face.
901
"""
902
g = self.parse(mv)
903
lst = self.facets(g)
904
line1 = " +--------------+\n"
905
line2 = " |%3d %3d %3d |\n"%(lst[0],lst[1],lst[2])
906
line3 = " |%3d top %3d |\n"%(lst[3],lst[4])
907
line4 = " |%3d %3d %3d |\n"%(lst[5],lst[6],lst[7])
908
line5 = "+------------+--------------+-------------+------------+\n"
909
line6 = "|%3d %3d %3d |%3d %3d %3d |%3d %3d %3d |%3d %3d %3d |\n"%(lst[8],lst[9],lst[10],lst[16],lst[17],lst[18],lst[24],lst[25],lst[26],lst[32],lst[33],lst[34])
910
line7 = "|%3d left%3d |%3d front%3d |%3d right%3d |%3d rear%3d |\n"%(lst[11],lst[12],lst[19],lst[20],lst[27],lst[28],lst[35],lst[36])
911
line8 = "|%3d %3d %3d |%3d %3d %3d |%3d %3d %3d |%3d %3d %3d |\n"%(lst[13],lst[14],lst[15],lst[21],lst[22],lst[23],lst[29],lst[30],lst[31],lst[37],lst[38],lst[39])
912
line9 = "+------------+--------------+-------------+------------+\n"
913
line10 = " |%3d %3d %3d |\n"%(lst[40],lst[41],lst[42])
914
line11 = " |%3d bottom%3d |\n"%(lst[43],lst[44])
915
line12 = " |%3d %3d %3d |\n"%(lst[45],lst[46],lst[47])
916
line13 = " +--------------+\n"
917
return line1+line2+line3+line4+line5+line6+line7+line8+line9+line10+line11+line12+line13
918
919
def plot_cube(self, mv, title=True, colors = [lpurple, yellow, red, green, orange, blue]):
920
r"""
921
Input the move mv, as a string in the Singmaster notation, and
922
output the 2D plot of the cube in that state.
923
924
Type ``P.show()`` to display any of the plots below.
925
926
EXAMPLES::
927
928
sage: rubik = CubeGroup()
929
sage: P = rubik.plot_cube("R^2*U^2*R^2*U^2*R^2*U^2", title = False)
930
sage: # (R^2U^2)^3 permutes 2 pairs of edges (uf,ub)(fr,br)
931
sage: P = rubik.plot_cube("R*L*D^2*B^3*L^2*F^2*R^2*U^3*D*R^3*D^2*F^3*B^3*D^3*F^2*D^3*R^2*U^3*F^2*D^3")
932
sage: # the superflip (in 20f* moves)
933
sage: P = rubik.plot_cube("U^2*F*U^2*L*R^(-1)*F^2*U*F^3*B^3*R*L*U^2*R*D^3*U*L^3*R*D*R^3*L^3*D^2")
934
sage: # "superflip+4 spot" (in 26q* moves)
935
"""
936
g = self.parse(mv)
937
state = self.facets(g)
938
#print state
939
cubies = [create_poly(index2singmaster(state[x]), color_of_square(x+1, colors)) for x in range(48)]
940
centers = [create_poly('%s_center' % "ulfrbd"[i], colors[i]) for i in range(6)]
941
clrs = sum(cubies) + sum(centers)
942
clrs.axes(show=False)
943
if title == True:
944
t = text('sagemath.org', (7.8,-3.5),rgbcolor=lgrey)
945
P = clrs+t
946
P.axes(show=False)
947
return P
948
return clrs
949
950
def plot3d_cube(self,mv,title=True):
951
r"""
952
Displays `F,U,R` faces of the cube after the given move ``mv``. Mostly
953
included for the purpose of drawing pictures and checking moves.
954
955
INPUT:
956
957
- ``mv`` -- A string in the Singmaster notation
958
- ``title`` -- (Default: ``True``) Display the title information
959
960
The first one below is "superflip+4 spot" (in 26q\* moves) and the
961
second one is the superflip (in 20f\* moves). Type show(P) to view
962
them.
963
964
EXAMPLES::
965
966
sage: rubik = CubeGroup()
967
sage: P = rubik.plot3d_cube("U^2*F*U^2*L*R^(-1)*F^2*U*F^3*B^3*R*L*U^2*R*D^3*U*L^3*R*D*R^3*L^3*D^2")
968
sage: P = rubik.plot3d_cube("R*L*D^2*B^3*L^2*F^2*R^2*U^3*D*R^3*D^2*F^3*B^3*D^3*F^2*D^3*R^2*U^3*F^2*D^3")
969
"""
970
g = self.parse(mv)
971
state = self.facets(g)
972
clr_any = white
973
shown_labels = range(1,9)+range(17,33)
974
clr = [color_of_square(state[c-1]) for c in shown_labels]
975
cubiesR = [plot3d_cubie(cubie_centers(c),cubie_colors(c,state)) for c in [32,31,30,29,28,27,26,25]]
976
cubeR = sum(cubiesR)
977
cubiesU = [plot3d_cubie(cubie_centers(c),cubie_colors(c,state)) for c in range(1,9)]
978
cubeU = sum(cubiesU)
979
cubiesF = [plot3d_cubie(cubie_centers(c),cubie_colors(c,state)) for c in [22,23,24,20,21]]
980
cubeF = sum(cubiesF)
981
centerR = polygon_plot3d([[1,3,1],[2,3,1],[2,3,2],[1,3,2],[1,3,1]],rgbcolor=green)
982
centerF = polygon_plot3d([[3,1,1],[3,2,1],[3,2,2],[3,1,2],[3,1,1]],rgbcolor=red)
983
centerU = polygon_plot3d([[1,1,3],[1,2,3],[2,2,3],[2,1,3],[1,1,3]],rgbcolor=lpurple)
984
centers = centerF+centerR+centerU
985
P = cubeR+cubeF+cubeU+centers
986
P.axes(show=False)
987
if title == True:
988
t1 = text('Up, Front, and Right faces. ' , (-0.2,-2.5))
989
t2 = text(' sagemath.org', (0.8,-3.1),rgbcolor=lgrey)
990
t3 = text(" ",(3.5,0),rgbcolor=white)
991
P = P+t1+t2+t3
992
P.axes(show=False)
993
return P
994
return P
995
996
def legal(self,state,mode="quiet"):
997
r"""
998
Return 1 (true) if the dictionary ``state`` (in the
999
same format as returned by the faces method) represents a legal
1000
position (or state) of the Rubik's cube or 0 (false)
1001
otherwise.
1002
1003
EXAMPLES::
1004
1005
sage: rubik = CubeGroup()
1006
sage: r0 = rubik.faces("")
1007
sage: r1 = {'back': [[33, 34, 35], [36, 0, 37], [38, 39, 40]], 'down': [[41, 42, 43], [44, 0, 45], [46, 47, 48]],'front': [[17, 18, 19], [20, 0, 21], [22, 23, 24]],'left': [[9, 10, 11], [12, 0, 13], [14, 15, 16]],'right': [[25, 26, 27], [28, 0, 29], [30, 31, 32]],'up': [[1, 2, 3], [4, 0, 5], [6, 8, 7]]}
1008
sage: rubik.legal(r0)
1009
1
1010
sage: rubik.legal(r0,"verbose")
1011
(1, ())
1012
sage: rubik.legal(r1)
1013
0
1014
"""
1015
try:
1016
g = self.parse(state)
1017
res = 1
1018
except TypeError:
1019
res = 0
1020
g = self.identity()
1021
if mode != 'quiet':
1022
return res, g
1023
else:
1024
return res
1025
1026
def solve(self,state, algorithm='default'):
1027
r"""
1028
Solves the cube in the ``state``, given as a dictionary
1029
as in ``legal``. See the ``solve`` method
1030
of the RubiksCube class for more details.
1031
1032
This may use GAP's ``EpimorphismFromFreeGroup`` and
1033
``PreImagesRepresentative`` as explained below, if
1034
'gap' is passed in as the algorithm.
1035
1036
This algorithm
1037
1038
1039
#. constructs the free group on 6 generators then computes a
1040
reasonable set of relations which they satisfy
1041
1042
#. computes a homomorphism from the cube group to this free group
1043
quotient
1044
1045
#. takes the cube position, regarded as a group element, and maps
1046
it over to the free group quotient
1047
1048
#. using those relations and tricks from combinatorial group theory
1049
(stabilizer chains), solves the "word problem" for that element.
1050
1051
#. uses python string parsing to rewrite that in cube notation.
1052
1053
1054
The Rubik's cube group has about `4.3 \times 10^{19}`
1055
elements, so this process is time-consuming. See
1056
http://www.gap-system.org/Doc/Examples/rubik.html for an
1057
interesting discussion of some GAP code analyzing the Rubik's
1058
cube.
1059
1060
EXAMPLES::
1061
1062
sage: rubik = CubeGroup()
1063
sage: state = rubik.faces("R")
1064
sage: rubik.solve(state)
1065
'R'
1066
sage: state = rubik.faces("R*U")
1067
sage: rubik.solve(state, algorithm='gap') # long time
1068
'R*U'
1069
1070
You can also check this another (but similar) way using the
1071
``word_problem`` method (eg, G = rubik.group(); g =
1072
G("(3,38,43,19)(5,36,45,21)(8,33,48,24)(25,27,32,30)(26,29,31,28)");
1073
g.word_problem([b,d,f,l,r,u]), though the output will be less
1074
intuitive).
1075
"""
1076
from sage.groups.perm_gps.permgroup import PermutationGroup
1077
from sage.interfaces.all import gap
1078
try:
1079
g = self.parse(state)
1080
except TypeError:
1081
return "Illegal or syntactically incorrect state. No solution."
1082
if algorithm != 'gap':
1083
C = RubiksCube(g)
1084
return C.solve(algorithm)
1085
1086
hom = self._gap_().EpimorphismFromFreeGroup()
1087
soln = hom.PreImagesRepresentative(gap(str(g)))
1088
# print soln
1089
sol = str(soln)
1090
names = self.gen_names()
1091
for i in range(6):
1092
sol = sol.replace("x%s" % (i+1), names[i])
1093
return sol
1094
1095
1096
##########################################################
1097
# 3d object generation
1098
##########################################################
1099
1100
def cubie_faces():
1101
"""
1102
This provides a map from the 6 faces of the 27 cubies to the 48
1103
facets of the larger cube.
1104
1105
-1,-1,-1 is left, top, front
1106
1107
EXAMPLES::
1108
1109
sage: from sage.groups.perm_gps.cubegroup import cubie_faces
1110
sage: sorted(cubie_faces().items())
1111
[((-1, -1, -1), [6, 17, 11, 0, 0, 0]),
1112
((-1, -1, 0), [4, 0, 10, 0, 0, 0]),
1113
((-1, -1, 1), [1, 0, 9, 0, 35, 0]),
1114
((-1, 0, -1), [0, 20, 13, 0, 0, 0]),
1115
((-1, 0, 0), [0, 0, -5, 0, 0, 0]),
1116
((-1, 0, 1), [0, 0, 12, 0, 37, 0]),
1117
((-1, 1, -1), [0, 22, 16, 41, 0, 0]),
1118
((-1, 1, 0), [0, 0, 15, 44, 0, 0]),
1119
((-1, 1, 1), [0, 0, 14, 46, 40, 0]),
1120
((0, -1, -1), [7, 18, 0, 0, 0, 0]),
1121
((0, -1, 0), [-6, 0, 0, 0, 0, 0]),
1122
((0, -1, 1), [2, 0, 0, 0, 34, 0]),
1123
((0, 0, -1), [0, -4, 0, 0, 0, 0]),
1124
((0, 0, 0), [0, 0, 0, 0, 0, 0]),
1125
((0, 0, 1), [0, 0, 0, 0, -2, 0]),
1126
((0, 1, -1), [0, 23, 0, 42, 0, 0]),
1127
((0, 1, 0), [0, 0, 0, -1, 0, 0]),
1128
((0, 1, 1), [0, 0, 0, 47, 39, 0]),
1129
((1, -1, -1), [8, 19, 0, 0, 0, 25]),
1130
((1, -1, 0), [5, 0, 0, 0, 0, 26]),
1131
((1, -1, 1), [3, 0, 0, 0, 33, 27]),
1132
((1, 0, -1), [0, 21, 0, 0, 0, 28]),
1133
((1, 0, 0), [0, 0, 0, 0, 0, -3]),
1134
((1, 0, 1), [0, 0, 0, 0, 36, 29]),
1135
((1, 1, -1), [0, 24, 0, 43, 0, 30]),
1136
((1, 1, 0), [0, 0, 0, 45, 0, 31]),
1137
((1, 1, 1), [0, 0, 0, 48, 38, 32])]
1138
"""
1139
faceR = [[25,26,27], [28,-3,29], [30,31,32]] # green
1140
faceL = [[ 9,10,11], [12,-5,13], [14,15,16]] # orange
1141
faceU = [[ 1, 2, 3], [ 4,-6, 5], [ 6, 7, 8]] # red
1142
faceD = [[41,42,43], [44,-1,45], [46,47,48]] # purple
1143
faceF = [[17,18,19], [20,-4,21], [22,23,24]] # yellow
1144
faceB = [[33,34,35], [36,-2,37], [38,39,40]] # blue
1145
cubies = {}
1146
for x in [-1,0,1]:
1147
for y in [-1,0,1]:
1148
for z in [-1,0,1]:
1149
cubies[x,y,z] = [0,0,0,0,0,0]
1150
1151
for i in [-1,0,1]:
1152
for j in [-1,0,1]:
1153
cubies[ i, j, -1][1] = faceF[1+j][1+i]
1154
cubies[ i, j, 1][4] = faceB[1+j][1-i]
1155
cubies[ i, -1, j][0] = faceU[1-j][1+i]
1156
cubies[ i, 1, j][3] = faceD[1+j][1+i]
1157
cubies[ -1, i, j][2] = faceL[1+i][1-j]
1158
cubies[ 1, i, j][5] = faceR[1+i][1+j]
1159
1160
return cubies
1161
1162
cubie_face_list = cubie_faces()
1163
1164
1165
rand_colors = [(RDF.random_element(), RDF.random_element(), RDF.random_element()) for _ in range(56)]
1166
1167
1168
class RubiksCube(SageObject):
1169
r"""
1170
The Rubik's cube (in a given state).
1171
1172
EXAMPLES::
1173
1174
sage: C = RubiksCube().move("R U R'")
1175
sage: C.show3d()
1176
1177
::
1178
1179
sage: C = RubiksCube("R*L"); C
1180
+--------------+
1181
| 17 2 38 |
1182
| 20 top 36 |
1183
| 22 7 33 |
1184
+------------+--------------+-------------+------------+
1185
| 11 13 16 | 41 18 3 | 27 29 32 | 48 34 6 |
1186
| 10 left 15 | 44 front 5 | 26 right 31 | 45 rear 4 |
1187
| 9 12 14 | 46 23 8 | 25 28 30 | 43 39 1 |
1188
+------------+--------------+-------------+------------+
1189
| 40 42 19 |
1190
| 37 bottom 21 |
1191
| 35 47 24 |
1192
+--------------+
1193
sage: C.show()
1194
sage: C.solve(algorithm='gap') # long time
1195
'L R'
1196
sage: C == RubiksCube("L*R")
1197
True
1198
"""
1199
def __init__(self, state=None, history=[], colors=[lpurple,yellow,red,green,orange,blue]):
1200
"""
1201
Initialize ``self``.
1202
1203
EXAMPLES::
1204
1205
sage: C = RubiksCube().move("R*U")
1206
sage: TestSuite(C).run()
1207
"""
1208
self.colors = colors
1209
self._history = history
1210
self._group = CubeGroup()
1211
if state is None:
1212
self._state = self._group.identity()
1213
else:
1214
if isinstance(state, str):
1215
state = self._group.faces(state)
1216
if not isinstance(state, PermutationGroupElement):
1217
legal, state = self._group.legal(state, mode="gimme_group_element")
1218
if not legal:
1219
raise ValueError("Not a legal cube.")
1220
self._state = state
1221
1222
def move(self, g):
1223
r"""
1224
Move the Rubik's cube by ``g``.
1225
1226
EXAMPLES::
1227
1228
sage: RubiksCube().move("R*U") == RubiksCube("R*U")
1229
True
1230
"""
1231
if not isinstance(g, self._group._element_class()):
1232
g = self._group.move(g)[0]
1233
return RubiksCube(self._state * g, self._history + [g], self.colors)
1234
1235
def undo(self):
1236
r"""
1237
Undo the last move of the Rubik's cube.
1238
1239
EXAMPLES::
1240
1241
sage: C = RubiksCube()
1242
sage: D = C.move("R*U")
1243
sage: D.undo() == C
1244
True
1245
"""
1246
if len(self._history) == 0:
1247
raise ValueError("no moves to undo")
1248
g = self._history[-1]
1249
return RubiksCube(self._state * ~g, self._history[:-1], self.colors)
1250
1251
def _repr_(self):
1252
r"""
1253
Return a string representation of ``self``.
1254
1255
EXAMPLES::
1256
1257
sage: RubiksCube().move("R*U")
1258
+--------------+
1259
| 3 5 38 |
1260
| 2 top 36 |
1261
| 1 4 25 |
1262
+------------+--------------+-------------+------------+
1263
| 33 34 35 | 9 10 8 | 19 29 32 | 48 26 27 |
1264
| 12 left 13 | 20 front 7 | 18 right 31 | 45 rear 37 |
1265
| 14 15 16 | 22 23 6 | 17 28 30 | 43 39 40 |
1266
+------------+--------------+-------------+------------+
1267
| 41 42 11 |
1268
| 44 bottom 21 |
1269
| 46 47 24 |
1270
+--------------+
1271
<BLANKLINE>
1272
"""
1273
return self._group.repr2d(self._state)
1274
1275
def facets(self):
1276
r"""
1277
Return the facets of ``self``.
1278
1279
EXAMPLES::
1280
1281
sage: C = RubiksCube("R*U")
1282
sage: C.facets()
1283
[3, 5, 38, 2, 36, 1, 4, 25, 33, 34, 35, 12, 13, 14, 15, 16, 9, 10,
1284
8, 20, 7, 22, 23, 6, 19, 29, 32, 18, 31, 17, 28, 30, 48, 26, 27,
1285
45, 37, 43, 39, 40, 41, 42, 11, 44, 21, 46, 47, 24]
1286
"""
1287
return self._group.facets(self._state)
1288
1289
def plot(self):
1290
r"""
1291
Return a plot of ``self``.
1292
1293
EXAMPLES::
1294
1295
sage: C = RubiksCube("R*U")
1296
sage: C.plot()
1297
"""
1298
return self._group.plot_cube(self._state)
1299
1300
def show(self):
1301
r"""
1302
Show a plot of ``self``.
1303
1304
EXAMPLES::
1305
1306
sage: C = RubiksCube("R*U")
1307
sage: C.show()
1308
"""
1309
self.plot().show()
1310
1311
def cubie(self, size, gap, x,y,z, colors, stickers=True):
1312
"""
1313
Return the cubie at `(x,y,z)`.
1314
1315
INPUT:
1316
1317
- ``size`` -- The size of the cubie
1318
- ``gap`` -- The gap between cubies
1319
- ``x,y,z`` -- The position of the cubie
1320
- ``colors`` -- The list of colors
1321
- ``stickers`` -- (Default ``True``) Boolean to display stickers
1322
1323
EXAMPLES::
1324
1325
sage: C = RubiksCube("R*U")
1326
sage: C.cubie(0.15, 0.025, 0,0,0, C.colors*3)
1327
"""
1328
sides = cubie_face_list[x,y,z]
1329
t = 2*size+gap
1330
my_colors = [colors[sides[i]+6] for i in range(6)]
1331
if stickers:
1332
B = Box(size, size, size, color=(.1, .1, .1))
1333
S = B + B.stickers(my_colors, size*.1, size*.01)
1334
return S.translate(-t*x, -t*z, -t*y)
1335
else:
1336
return ColorCube(size, [colors[sides[i]+6] for i in range(6)]).translate(-t*x, -t*z, -t*y)
1337
1338
def plot3d(self, stickers=True):
1339
r"""
1340
Return a 3D plot of ``self``.
1341
1342
EXAMPLES::
1343
1344
sage: C = RubiksCube("R*U")
1345
sage: C.plot3d()
1346
"""
1347
while len(self.colors) < 7:
1348
self.colors.append((.1, .1, .1))
1349
side_colors = [Texture(color=c, ambient=.75) for c in self.colors]
1350
start_colors = sum([[c]*8 for c in side_colors], [])
1351
facets = self._group.facets(self._state)
1352
facet_colors = [0]*48
1353
for i in range(48):
1354
facet_colors[facets[i]-1] = start_colors[i]
1355
all_colors = side_colors + facet_colors
1356
pm = [-1,0,1]
1357
C = sum([self.cubie(.15, .025, x, y, z, all_colors, stickers) for x in pm for y in pm for z in pm], Box(.35, .35, .35, color=self.colors[-1]))
1358
return C.rotateZ(1.5) #.scale([1,-1,1]).rotateZ(1.5)
1359
1360
def show3d(self):
1361
r"""
1362
Show a 3D plot of ``self``.
1363
1364
EXAMPLES::
1365
1366
sage: C = RubiksCube("R*U")
1367
sage: C.show3d()
1368
"""
1369
return self.plot3d().show()
1370
1371
def __cmp__(self, other):
1372
"""
1373
Comparison.
1374
1375
EXAMPLES::
1376
1377
sage: C = RubiksCube()
1378
sage: D = RubiksCube("R*U")
1379
sage: C < D
1380
True
1381
sage: C > D
1382
False
1383
sage: C == C
1384
True
1385
sage: C != D
1386
True
1387
"""
1388
c = cmp(type(self), type(other))
1389
if c == 0:
1390
return cmp(self._state, other._state)
1391
else:
1392
return c
1393
1394
def solve(self, algorithm="hybrid", timeout=15):
1395
r"""
1396
Solve the Rubik's cube.
1397
1398
INPUT:
1399
1400
- ``algorithm`` -- must be one of the following:
1401
1402
- ``hybrid`` - try ``kociemba`` for timeout seconds, then ``dietz``
1403
- ``kociemba`` - Use Dik T. Winter's program
1404
(reasonable speed, few moves)
1405
- ``dietz`` - Use Eric Dietz's cubex program
1406
(fast but lots of moves)
1407
- ``optimal`` - Use Michael Reid's optimal program
1408
(may take a long time)
1409
- ``gap`` - Use GAP word solution (can be slow)
1410
1411
EXAMPLES::
1412
1413
sage: C = RubiksCube("R U F L B D")
1414
sage: C.solve()
1415
'R U F L B D'
1416
1417
Dietz's program is much faster, but may give highly non-optimal
1418
solutions::
1419
1420
sage: s = C.solve('dietz'); s
1421
"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'"
1422
sage: C2 = RubiksCube(s)
1423
sage: C == C2
1424
True
1425
"""
1426
import sage.interfaces.rubik # here to avoid circular referencing
1427
if algorithm == "default":
1428
algorithm = "hybrid"
1429
1430
if algorithm == "hybrid":
1431
try:
1432
solver = sage.interfaces.rubik.DikSolver()
1433
return solver.solve(self.facets(), timeout=timeout)
1434
except RuntimeError:
1435
solver = sage.interfaces.rubik.CubexSolver()
1436
return solver.solve(self.facets())
1437
1438
elif algorithm == "kociemba":
1439
solver = sage.interfaces.rubik.DikSolver()
1440
return solver.solve(self.facets(), timeout=timeout)
1441
1442
elif algorithm == "dietz":
1443
solver = sage.interfaces.rubik.CubexSolver()
1444
return solver.solve(self.facets())
1445
1446
elif algorithm == "optimal":
1447
# TODO: cache this, startup is expensive
1448
solver = sage.interfaces.rubik.OptimalSolver()
1449
return solver.solve(self.facets())
1450
1451
elif algorithm == "gap":
1452
solver = CubeGroup()
1453
return solver.solve(self._state)
1454
1455
else:
1456
raise ValueError, "Unrecognized algorithm: %s" % algorithm
1457
1458
def scramble(self, moves=30):
1459
"""
1460
Scramble the Rubik's cube.
1461
1462
EXAMPLES::
1463
1464
sage: C = RubiksCube()
1465
sage: C.scramble() # random
1466
+--------------+
1467
| 38 29 35 |
1468
| 20 top 42 |
1469
| 11 44 30 |
1470
+------------+--------------+-------------+------------+
1471
| 48 13 17 | 6 15 24 | 43 23 9 | 1 36 32 |
1472
| 4 left 18 | 7 front 37 | 12 right 26 | 5 rear 10 |
1473
| 33 31 40 | 14 28 8 | 25 47 16 | 22 2 3 |
1474
+------------+--------------+-------------+------------+
1475
| 46 21 19 |
1476
| 45 bottom 39 |
1477
| 27 34 41 |
1478
+--------------+
1479
<BLANKLINE>
1480
"""
1481
last_move = move = " "
1482
all = []
1483
for i in range(moves):
1484
while move[0] == last_move[0]:
1485
move = "RLUDBF"[random.randint(0,5)] + " '2"[random.randint(0,2)]
1486
last_move = move
1487
all.append(move)
1488
return self.move(' '.join(all))
1489
1490
1491