def run(self): cb = None if self.rot == 0: # no rotation cb = cubie.CubieCube(self.cb_cube.cp, self.cb_cube.co, self.cb_cube.ep, self.cb_cube.eo) elif self.rot == 1: # conjugation by 120° rotation cb = cubie.CubieCube(sy.symCube[32].cp, sy.symCube[32].co, sy.symCube[32].ep, sy.symCube[32].eo) cb.multiply(self.cb_cube) cb.multiply(sy.symCube[16]) elif self.rot == 2: # conjugation by 240° rotation cb = cubie.CubieCube(sy.symCube[16].cp, sy.symCube[16].co, sy.symCube[16].ep, sy.symCube[16].eo) cb.multiply(self.cb_cube) cb.multiply(sy.symCube[32]) if self.inv == 1: # invert cube tmp = cubie.CubieCube() cb.inv_cubie_cube(tmp) cb = tmp self.co_cube = coord.CoordCube( cb) # the rotated/inverted cube in coordinate representation dist = self.co_cube.get_depth_phase1() for togo1 in range( dist, 20): # iterative deepening, solution has at least dist moves self.sofar_phase1 = [] self.search(self.co_cube.flip, self.co_cube.twist, self.co_cube.slice_sorted, dist, togo1)
def create_phase2_edgemerge_table(): """phase2_edgemerge retrieves the initial phase 2 ud_edges coordinate from the u_edges and d_edges coordinates.""" fname = "phase2_edgemerge" global u_edges_plus_d_edges_to_ud_edges c_u = cb.CubieCube() c_d = cb.CubieCube() c_ud = cb.CubieCube() edge_u = [Ed.UR, Ed.UF, Ed.UL, Ed.UB] edge_d = [Ed.DR, Ed.DF, Ed.DL, Ed.DB] edge_ud = [Ed.UR, Ed.UF, Ed.UL, Ed.UB, Ed.DR, Ed.DF, Ed.DL, Ed.DB] if not path.isfile(path.join(FOLDER, fname)): cnt = 0 print("creating " + fname + " table...") u_edges_plus_d_edges_to_ud_edges = ar.array( 'H', [0 for _ in range(N_U_EDGES_PHASE2 * N_PERM_4)]) for i in range(N_U_EDGES_PHASE2): c_u.set_u_edges(i) for j in range(N_CHOOSE_8_4): c_d.set_d_edges(j * N_PERM_4) invalid = False for e in edge_ud: c_ud.ep[e] = -1 # invalidate edges if c_u.ep[e] in edge_u: c_ud.ep[e] = c_u.ep[e] if c_d.ep[e] in edge_d: c_ud.ep[e] = c_d.ep[e] if c_ud.ep[e] == -1: invalid = True # edge collision break if not invalid: for k in range(N_PERM_4): c_d.set_d_edges(j * N_PERM_4 + k) for e in edge_ud: if c_u.ep[e] in edge_u: c_ud.ep[e] = c_u.ep[e] if c_d.ep[e] in edge_d: c_ud.ep[e] = c_d.ep[e] u_edges_plus_d_edges_to_ud_edges[ N_PERM_4 * i + k] = c_ud.get_ud_edges() cnt += 1 if cnt % 2000 == 0: print('.', end='', flush=True) print() fh = open(path.join(FOLDER, fname), "wb") u_edges_plus_d_edges_to_ud_edges.tofile(fh) fh.close() print() else: print("loading " + fname + " table...") fh = open(path.join(FOLDER, fname), "rb") u_edges_plus_d_edges_to_ud_edges = ar.array('H') u_edges_plus_d_edges_to_ud_edges.fromfile(fh, N_U_EDGES_PHASE2 * N_PERM_4)
def solveto(cubestring, goalstring, max_length=20, timeout=3): """Solve a cube defined by cubstring to a position defined by goalstring. :param cubestring: The format of the string is given in the Facelet class defined in the file enums.py :param goalstring: The format of the string is given in the Facelet class defined in the file enums.py :param max_length: The function will return if a maneuver of length <= max_length has been found :param timeout: If the function times out, the best solution found so far is returned. If there has not been found any solution yet the computation continues until a first solution appears. """ fc0 = face.FaceCube() fcg = face.FaceCube() s = fc0.from_string(cubestring) if s != cubie.CUBE_OK: return 'first cube ' + s # no valid cubestring, gives invalid facelet cube s = fcg.from_string(goalstring) if s != cubie.CUBE_OK: return 'second cube ' + s # no valid goalstring, gives invalid facelet cube cc0 = fc0.to_cubie_cube() s = cc0.verify() if s != cubie.CUBE_OK: return 'first cube ' + s # no valid facelet cube, gives invalid cubie cube ccg = fcg.to_cubie_cube() s = ccg.verify() if s != cubie.CUBE_OK: return 'second cube ' + s # no valid facelet cube, gives invalid cubie cube # cc0 * S = ccg <=> (ccg^-1 * cc0) * S = Id cc = cubie.CubieCube() ccg.inv_cubie_cube(cc) cc.multiply(cc0) my_threads = [] s_time = time.monotonic() # these mutable variables are modidified by all six threads s_length = [999] solutions = [] terminated = thr.Event() terminated.clear() syms = cc.symmetries() if len(list({16, 20, 24, 28} & set(syms)) ) > 0: # we have some rotational symmetry along a long diagonal tr = [0, 3] # so we search only one direction and the inverse else: tr = range(6) # This means search in 3 directions + inverse cube if len(list(set(range(48, 96)) & set(syms)) ) > 0: # we have some antisymmetry so we do not search the inverses tr = list(filter(lambda x: x < 3, tr)) for i in tr: th = SolverThread(cc, i % 3, i // 3, max_length, timeout, s_time, solutions, terminated, [999]) my_threads.append(th) th.start() for t in my_threads: t.join() # wait until all threads have finished s = '' if len(solutions) > 0: for m in solutions[-1]: # the last solution is the shortest s += m.name + ' ' return s + '(' + str(len(s) // 3) + 'f)'
def random(): """Generate a random cube and set the corresponding facelet colors.""" cc = cubie.CubieCube() cc.randomize() fc = cc.to_facelet_cube() idx = 0 for f in range(6): for row in range(3): for col in range(3): canvas.itemconfig(facelet_id[f][row][col], fill=cols[fc.f[idx]]) idx += 1
def test(n, t): """ :param n: The number of generated random cubes :param t: The time in seconds to spend on each cube :return: A dictionary with the solving statistics """ cc = cubie.CubieCube() cnt = [0] * 31 for i in range(n): cc.randomize() fc = cc.to_facelet_cube() s = fc.to_string() print(s) s = sv.solve(s, 0, t) print(s) print() cnt[int(s.split('(')[1].split('f')[0])] += 1 avr = 0 for i in range(31): avr += i * cnt[i] avr /= n return 'average ' + '%.2f' % avr + ' moves', dict(zip(range(31), cnt))
epROT_F2 = [Ed.DL, Ed.DF, Ed.DR, Ed.DB, Ed.UL, Ed.UF, Ed.UR, Ed.UB, Ed.FL, Ed.FR, Ed.BR, Ed.BL] eoROT_F2 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] # 90° clockwise rotation around the axis through the U and D centers cpROT_U4 = [Co.UBR, Co.URF, Co.UFL, Co.ULB, Co.DRB, Co.DFR, Co.DLF, Co.DBL] coROT_U4 = [0, 0, 0, 0, 0, 0, 0, 0] epROT_U4 = [Ed.UB, Ed.UR, Ed.UF, Ed.UL, Ed.DB, Ed.DR, Ed.DF, Ed.DL, Ed.BR, Ed.FR, Ed.FL, Ed.BL] eoROT_U4 = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1] # reflection at the plane through the U, D, F, B centers cpMIRR_LR2 = [Co.UFL, Co.URF, Co.UBR, Co.ULB, Co.DLF, Co.DFR, Co.DRB, Co.DBL] coMIRR_LR2 = [3, 3, 3, 3, 3, 3, 3, 3] epMIRR_LR2 = [Ed.UL, Ed.UF, Ed.UR, Ed.UB, Ed.DL, Ed.DF, Ed.DR, Ed.DB, Ed.FL, Ed.FR, Ed.BR, Ed.BL] eoMIRR_LR2 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] basicSymCube = [cb.CubieCube()] * 4 basicSymCube[BS.ROT_URF3] = cb.CubieCube(cpROT_URF3, coROT_URF3, epROT_URF3, eoROT_URF3) basicSymCube[BS.ROT_F2] = cb.CubieCube(cpROT_F2, coROT_F2, epROT_F2, eoROT_F2) basicSymCube[BS.ROT_U4] = cb.CubieCube(cpROT_U4, coROT_U4, epROT_U4, eoROT_U4) basicSymCube[BS.MIRR_LR2] = cb.CubieCube(cpMIRR_LR2, coMIRR_LR2, epMIRR_LR2, eoMIRR_LR2) # ###################################################################################################################### # ######################################## Fill SymCube list ########################################################### # 48 CubieCubes will represent the 48 cube symmetries symCube = [] cc = cb.CubieCube() # Identity cube idx = 0 for urf3 in range(3): for f2 in range(2): for u4 in range(4):
def create_phase1_prun_table(): """Create/load the flipslice_twist_depth3 pruning table for phase 1.""" global flipslice_twist_depth3 total = defs.N_FLIPSLICE_CLASS * defs.N_TWIST fname = "phase1_prun" if not path.isfile(path.join(defs.FOLDER, fname)): print("creating " + fname + " table...") print( 'This may take half an hour or even longer, depending on the hardware.' ) flipslice_twist_depth3 = ar.array('L', [0xffffffff] * (total // 16 + 1)) # #################### create table with the symmetries of the flipslice classes ############################### cc = cb.CubieCube() fs_sym = ar.array('H', [0] * defs.N_FLIPSLICE_CLASS) for i in range(defs.N_FLIPSLICE_CLASS): if (i + 1) % 1000 == 0: print('.', end='', flush=True) rep = sy.flipslice_rep[i] cc.set_slice(rep // defs.N_FLIP) cc.set_flip(rep % defs.N_FLIP) for s in range(defs.N_SYM_D4h): ss = cb.CubieCube(sy.symCube[s].cp, sy.symCube[s].co, sy.symCube[s].ep, sy.symCube[s].eo) # copy cube ss.edge_multiply(cc) # s*cc ss.edge_multiply(sy.symCube[sy.inv_idx[s]]) # s*cc*s^-1 if ss.get_slice() == rep // defs.N_FLIP and ss.get_flip( ) == rep % defs.N_FLIP: fs_sym[i] |= 1 << s print() # ################################################################################################################## fs_classidx = 0 # value for solved phase 1 twist = 0 set_flipslice_twist_depth3(defs.N_TWIST * fs_classidx + twist, 0) done = 1 depth = 0 backsearch = False print('depth:', depth, 'done: ' + str(done) + '/' + str(total)) while done != total: depth3 = depth % 3 if depth == 9: # backwards search is faster for depth >= 9 print('flipping to backwards search...') backsearch = True if depth < 8: mult = 5 # controls the output a few lines below else: mult = 1 idx = 0 for fs_classidx in range(defs.N_FLIPSLICE_CLASS): if (fs_classidx + 1) % (200 * mult) == 0: print('.', end='', flush=True) if (fs_classidx + 1) % (16000 * mult) == 0: print('') twist = 0 while twist < defs.N_TWIST: # ########## if table entries are not populated, this is very fast: ################################ if not backsearch and idx % 16 == 0 and flipslice_twist_depth3[idx // 16] == 0xffffffff \ and twist < defs.N_TWIST - 16: twist += 16 idx += 16 continue #################################################################################################### if backsearch: match = (get_flipslice_twist_depth3(idx) == 3) else: match = (get_flipslice_twist_depth3(idx) == depth3) if match: flipslice = sy.flipslice_rep[fs_classidx] flip = flipslice % 2048 # defs.N_FLIP = 2048 slice_ = flipslice >> 11 # // defs.N_FLIP for m in enums.Move: twist1 = mv.twist_move[18 * twist + m] # defs.N_MOVE = 18 flip1 = mv.flip_move[18 * flip + m] slice1 = mv.slice_sorted_move[ 432 * slice_ + m] // 24 # defs.N_PERM_4 = 24, 18*24 = 432 flipslice1 = (slice1 << 11) + flip1 fs1_classidx = sy.flipslice_classidx[flipslice1] fs1_sym = sy.flipslice_sym[flipslice1] twist1 = sy.twist_conj[(twist1 << 4) + fs1_sym] idx1 = 2187 * fs1_classidx + twist1 # defs.N_TWIST = 2187 if not backsearch: if get_flipslice_twist_depth3( idx1) == 3: # entry not yet filled set_flipslice_twist_depth3( idx1, (depth + 1) % 3) done += 1 # ####symmetric position has eventually more than one representation ############### sym = fs_sym[fs1_classidx] if sym != 1: for k in range(1, 16): sym >>= 1 if sym % 2 == 1: twist2 = sy.twist_conj[ (twist1 << 4) + k] # fs2_classidx = fs1_classidx due to symmetry idx2 = 2187 * fs1_classidx + twist2 if get_flipslice_twist_depth3( idx2) == 3: set_flipslice_twist_depth3( idx2, (depth + 1) % 3) done += 1 #################################################################################### else: # backwards search if get_flipslice_twist_depth3(idx1) == depth3: set_flipslice_twist_depth3( idx, (depth + 1) % 3) done += 1 break twist += 1 idx += 1 # idx = defs.N_TWIST * fs_class + twist depth += 1 print() print('depth:', depth, 'done: ' + str(done) + '/' + str(total)) fh = open(path.join(defs.FOLDER, fname), "wb") flipslice_twist_depth3.tofile(fh) else: print("loading " + fname + " table...") fh = open(path.join(defs.FOLDER, fname), "rb") flipslice_twist_depth3 = ar.array('L') flipslice_twist_depth3.fromfile(fh, total // 16 + 1) fh.close()
def create_phase2_prun_table(): """Create/load the corners_ud_edges_depth3 pruning table for phase 2.""" total = defs.N_CORNERS_CLASS * defs.N_UD_EDGES fname = "phase2_prun" global corners_ud_edges_depth3 if not path.isfile(path.join(defs.FOLDER, fname)): print("creating " + fname + " table...") corners_ud_edges_depth3 = ar.array('L', [0xffffffff] * (total // 16)) # ##################### create table with the symmetries of the corners classes ################################ cc = cb.CubieCube() c_sym = ar.array('H', [0] * defs.N_CORNERS_CLASS) for i in range(defs.N_CORNERS_CLASS): if (i + 1) % 1000 == 0: print('.', end='', flush=True) rep = sy.corner_rep[i] cc.set_corners(rep) for s in range(defs.N_SYM_D4h): ss = cb.CubieCube(sy.symCube[s].cp, sy.symCube[s].co, sy.symCube[s].ep, sy.symCube[s].eo) # copy cube ss.corner_multiply(cc) # s*cc ss.corner_multiply(sy.symCube[sy.inv_idx[s]]) # s*cc*s^-1 if ss.get_corners() == rep: c_sym[i] |= 1 << s print() ################################################################################################################ c_classidx = 0 # value for solved phase 2 ud_edge = 0 set_corners_ud_edges_depth3(defs.N_UD_EDGES * c_classidx + ud_edge, 0) done = 1 depth = 0 print('depth:', depth, 'done: ' + str(done) + '/' + str(total)) while depth < 10: # we fill the table only do depth 9 + 1 depth3 = depth % 3 idx = 0 mult = 2 if depth > 9: mult = 1 for c_classidx in range(defs.N_CORNERS_CLASS): if (c_classidx + 1) % (20 * mult) == 0: print('.', end='', flush=True) if (c_classidx + 1) % (1600 * mult) == 0: print('') ud_edge = 0 while ud_edge < defs.N_UD_EDGES: # ################ if table entries are not populated, this is very fast: ########################## if idx % 16 == 0 and corners_ud_edges_depth3[idx // 16] == 0xffffffff \ and ud_edge < defs.N_UD_EDGES - 16: ud_edge += 16 idx += 16 continue #################################################################################################### if get_corners_ud_edges_depth3(idx) == depth3: corner = sy.corner_rep[c_classidx] # only iterate phase 2 moves for m in (enums.Move.U1, enums.Move.U2, enums.Move.U3, enums.Move.R2, enums.Move.F2, enums.Move.D1, enums.Move.D2, enums.Move.D3, enums.Move.L2, enums.Move.B2): ud_edge1 = mv.ud_edges_move[18 * ud_edge + m] corner1 = mv.corners_move[18 * corner + m] c1_classidx = sy.corner_classidx[corner1] c1_sym = sy.corner_sym[corner1] ud_edge1 = sy.ud_edges_conj[(ud_edge1 << 4) + c1_sym] idx1 = 40320 * c1_classidx + ud_edge1 # N_UD_EDGES = 40320 if get_corners_ud_edges_depth3( idx1) == 3: # entry not yet filled set_corners_ud_edges_depth3( idx1, (depth + 1) % 3) # depth + 1 <= 10 done += 1 # ######symmetric position has eventually more than one representation ############# sym = c_sym[c1_classidx] if sym != 1: for k in range(1, 16): sym >>= 1 if sym % 2 == 1: ud_edge2 = sy.ud_edges_conj[ (ud_edge1 << 4) + k] # c1_classidx does not change idx2 = 40320 * c1_classidx + ud_edge2 if get_corners_ud_edges_depth3( idx2) == 3: set_corners_ud_edges_depth3( idx2, (depth + 1) % 3) done += 1 #################################################################################### ud_edge += 1 idx += 1 # idx = defs.N_UD_EDGEPERM * corner_classidx + ud_edge depth += 1 print() print('depth:', depth, 'done: ' + str(done) + '/' + str(total)) print('remaining unfilled entries have depth >=11') fh = open(path.join(defs.FOLDER, fname), "wb") corners_ud_edges_depth3.tofile(fh) else: print("loading " + fname + " table...") fh = open(path.join(defs.FOLDER, fname), "rb") corners_ud_edges_depth3 = ar.array('L') corners_ud_edges_depth3.fromfile(fh, total // 16) fh.close()
# ################### Movetables describe the transformation of the coordinates by cube moves. ######################### from os import path import array as ar import twophase.cubie as cb import twophase.enums as enums from twophase.defs import FOLDER, N_TWIST, N_FLIP, N_SLICE_SORTED, N_CORNERS, N_UD_EDGES, N_MOVE a = cb.CubieCube() # ######################################### Move table for the twists of the corners. ################################## # The twist coordinate describes the 3^7 = 2187 possible orientations of the 8 corners # 0 <= twist < 2187 in phase 1, twist = 0 in phase 2 fname = "move_twist" if not path.isfile(path.join(FOLDER, fname)): print("creating " + fname + " table...") twist_move = ar.array('H', [0 for i in range(N_TWIST * N_MOVE)]) for i in range(N_TWIST): a.set_twist(i) for j in enums.Color: # six faces U, R, F, D, L, B for k in range(3): # three moves for each face, for example U, U2, U3 = U' a.corner_multiply(cb.basicMoveCube[j]) twist_move[N_MOVE * i + 3 * j + k] = a.get_twist() a.corner_multiply(cb.basicMoveCube[j]) # 4. move restores face fh = open(path.join(FOLDER, fname), "wb") twist_move.tofile(fh) else: print("loading " + fname + " table...") fh = open(path.join(FOLDER, fname), "rb") twist_move = ar.array('H') twist_move.fromfile(fh, N_TWIST * N_MOVE)