import math, pickle, random, time, numpy from sympy.combinatorics import * from sympy.matrices import * from sympy.combinatorics.perm_groups import PermutationGroup from sympy.combinatorics.named_groups import * from operator import itemgetter #DESCRIPTION RS4x4 Explorer #AUTHOR Jim Johnston COPY 2013-2014 #EMAIL jimmy.johnston@gmail.com #LAST MODIFIED 2015-12-05 #VERSION 0.5 #Runs on Python 3.0 and greater #DEPENDENCY sympy needs to be installed #NOTES The functions below are designed for the specific analysis of the Rubik's Slide 4x4 Easy and Hard modes. Both modes are an algebraic group where the easy mode is generated by the elements and the hard mode by . Examples are provided at the bottom of the file on usage. #game rules defined using sympy permutations I = Permutation(16) C90 = Permutation(1,4,16,13)(2,8,15,9)(3,12,14,5)(6,7,11,10) C = Permutation(1,2,3,4,8,12,16,15,14,13,9,5)(6,7,11,10) R = Permutation(1,2,3,4)(5,6,7,8)(9,10,11,12)(13,14,15,16) U = Permutation(1,13,9,5)(2,14,10,6)(3,15,11,7)(4,16,12,8) T = Permutation(1,2) def shift(e,v): #sfinds index of element e in list v returns next element return(v[(v.index(e)+1)%4]) def shiftinv(e,v): #finds index of element e in list v returns previous element return(v[(v.index(e)-1)%4]) def right(e): #right shift using lists v1 = [1, 2, 3, 4] v2 = [5, 6, 7, 8] v3 = [9, 10, 11, 12] v4 = [13, 14, 15, 16] new_e = [] #resulting configuration return list for i in e: if i in v1: new_e.append(shift(i,v1)) elif i in v2: new_e.append(shift(i,v2)) elif i in v3: new_e.append(shift(i,v3)) elif i in v4: new_e.append(shift(i,v4)) return(new_e) def up(e): #uo shift using lists v1 = [1, 13, 9, 5] v2 = [2, 14, 10, 6] v3 = [3, 15, 11, 7] v4 = [4, 16, 12, 8] new_e = [] for i in e: if i in v1: new_e.append(shift(i,v1)) elif i in v2: new_e.append(shift(i,v2)) elif i in v3: new_e.append(shift(i,v3)) elif i in v4: new_e.append(shift(i,v4)) return(new_e) def c_easy(e): #clockwise 90 degree rotation using lists v1 = [1, 4, 16, 13] v2 = [2, 8, 15, 9] v3 = [3, 12, 14, 5] v4 = [6, 7, 11, 10] #to handle multiple elements run through a loop that computes each piece individually new_e = [] for i in e: if i in v1: new_e.append(shift(i,v1)) elif i in v2: new_e.append(shift(i,v2)) elif i in v3: new_e.append(shift(i,v3)) elif i in v4: new_e.append(shift(i,v4)) return(new_e) def c(e): #clockwise rotation using lists v1 = [1, 2, 3, 4, 8, 12, 16, 15, 14, 13, 9, 5] v2 = [6, 7, 11, 10] #to handle multiple elements run through a loop that computes each piece individually new_e = [] for i in e: if i in v1: new_e.append(v1[(v1.index(i)+1)%12]) elif i in v2: new_e.append(shift(i,v2)) return(new_e) def rightinv(e): #right inverse (left) shift using lists v1 = [1, 2, 3, 4] v2 = [5, 6, 7, 8] v3 = [9, 10, 11, 12] v4 = [13, 14, 15, 16] new_e = [] for i in e: if i in v1: new_e.append(shiftinv(i,v1)) elif i in v2: new_e.append(shiftinv(i,v2)) elif i in v3: new_e.append(shiftinv(i,v3)) elif i in v4: new_e.append(shiftinv(i,v4)) return(new_e) def upinv(e): #up inverse (down) shift using lists v1 = [1, 13, 9, 5] v2 = [2, 14, 10, 6] v3 = [3, 15, 11, 7] v4 = [4, 16, 12, 8] new_e = [] for i in e: if i in v1: new_e.append(shiftinv(i,v1)) elif i in v2: new_e.append(shiftinv(i,v2)) elif i in v3: new_e.append(shiftinv(i,v3)) elif i in v4: new_e.append(shiftinv(i,v4)) return(new_e) def c_easyinv(e): #clockwise 90 degree inverse (counterclockwise) rotation using lists v1 = [1, 4, 16, 13] v2 = [2, 8, 15, 9] v3 = [3, 12, 14, 5] v4 = [6, 7, 11, 10] #to handle multiple elements run through a loop that computes each piece individually new_e = [] for i in e: if i in v1: new_e.append(shiftinv(i,v1)) elif i in v2: new_e.append(shiftinv(i,v2)) elif i in v3: new_e.append(shiftinv(i,v3)) elif i in v4: new_e.append(shiftinv(i,v4)) return(new_e) def c_inv(e): #clockwise inverse (counterclockwise) rotation using lists v1 = [1, 2, 3, 4, 8, 12, 16, 15, 14, 13, 9, 5] v2 = [6, 7, 11, 10] #to handle multiple elements run through a loop that computes each piece individually new_e = [] for i in e: if i in v1: new_e.append(v1[(v1.index(i)-1)%12]) elif i in v2: new_e.append(shiftinv(i,v2)) return(new_e) #for ease of use, pre-defined game rules easymoves = [right,up,c_easy,rightinv,upinv,c_easyinv] hardmoves = [right,up,c,rightinv,upinv,c_inv] def one_color_game(mixed_list): #Determines the number of blocks being played for sorting purposes if len(mixed_list[0])==1: gamekey = itemgetter(0) elif len(mixed_list[0])==2: gamekey = itemgetter(0,1) elif len(mixed_list[0])==3: gamekey = itemgetter(0,1,2) elif len(mixed_list[0])==4: gamekey = itemgetter(0,1,2,3) elif len(mixed_list[0])==5: gamekey = itemgetter(0,1,2,3,4) elif len(mixed_list[0])==6: gamekey = itemgetter(0,1,2,3,4,5) elif len(mixed_list[0])==7: gamekey = itemgetter(0,1,2,3,4,5,6) elif len(mixed_list[0])==8: gamekey = itemgetter(0,1,2,3,4,5,6,7) #first for loop sorts each individual position, ex. [1,3,2] >> [1,2,3] #second for loop puts only unique positions into list and then returns sorted list #reasons behind second loop is that with single color [1,3,2] and [1,2,3] are the same. new_mixed_list = [] for element in mixed_list: element.sort() for element in mixed_list: if element not in new_mixed_list: new_mixed_list.append(element) return(sorted(new_mixed_list, key=gamekey)) def sort_element_list(element_list): #used by permutations2 function sortedlist = [] for element in element_list: newelement = [] for subelement in element: newelement.append(sorted(subelement)) if newelement not in sortedlist: sortedlist.append(newelement) return(sortedlist) def permutations(element,moves): #purpose: used to find all permutations of a given initial single color configuration #element is a list of colored boxes, moves are a list of transformations defined by lists list_of_elements_in_subgame = [] #will contain all elements in specific subgame current_element_location = [element] #once empty implies coset has been completed while(current_element_location): #while there are elements in set element = current_element_location[-1] #use the last element in set current_element_location.pop() #remove element being used from set if element not in list_of_elements_in_subgame: #if element not in result list then do list_of_elements_in_subgame.append(element) #add element to resulting list for move in moves: new_element = move(element) #track each next position given all moves if new_element not in current_element_location: if new_element not in list_of_elements_in_subgame: current_element_location.append(new_element) #as long as this element exists no where in either set then it is a new element to explore next steps return(one_color_game(list_of_elements_in_subgame)) def permutations2(element,moves): #purpose: generates all configurations of a single multi-colored game given an initial configuration (list) and transformations list_of_elements_in_coset = [] #will contain all elements in specific coset current_element_location = [element] #once empty implies coset has been completed colorcount = len(element) while(current_element_location): #while there are elements in set element = current_element_location[-1] #use the last element in set current_element_location.pop() #remove element being used from set if element not in list_of_elements_in_coset: #if element not in result list then do list_of_elements_in_coset.append(element) #add element to resulting list for move in moves: new_element = [] for i in range(colorcount): #critical piece to handle multi-color positions new_element.append(move(element[i])) #new_element = [move(element[0]),move(element[1])] #track each next position given all moves if new_element not in current_element_location: if new_element not in list_of_elements_in_coset: current_element_location.append(new_element) #as long as this element exists no where in either set then it is a new element to explore next steps return(sort_element_list(list_of_elements_in_coset)) def isAdjacent(i,j,moves): #purpose: determine boolean truth value of whether two elements are adjacent, support multi-color configurations #method: take element i, calculate its neighbors by applying transformations to it after each transformation check to see if element j is in result if it is no need to continue performing transformations #assumptions: it is assumed that both i and j are written as a sorted list for move in moves: new_element = [] for sub in i: new_element.append(sorted(move(sub))) if j==new_element: return(True) return(False) def adjacencyMatrix(elementlist,moves): #purpose: given a list of elements and transformations on them return an adjacency matrix in the form of a sympy matrix object build_list_for_matrix = [] for elementI in elementlist: for elementJ in elementlist: if isAdjacent(elementI,elementJ,moves): build_list_for_matrix.append(1) #print(elementI,elementJ,1) else: build_list_for_matrix.append(0) #print(elementI,elementJ,0) M = Matrix(len(elementlist),len(elementlist),build_list_for_matrix) return(M) def gamelist(n,moves,verbose=False): #purpose: generates all unique subgames of a defined game #vars: moves the permutations that define the group along with their inverses #limitations: currently works for one colored games only #notes: this function is necessarily long, instead of condensing the code it is purposely left long for readability and understanding games = [] if n==2: #2 blocks print("Analyzing 2 colored blocks games") for i in range(2,17): initialconfig = [1,i] game_exists = False for game in games: if initialconfig in game: game_exists = True break if not game_exists: contender = permutations(initialconfig,moves) contender.sort() games.append(contender) print(str(contender[0])+str(len(contender))) if verbose else None print("completed") elif n==3: #3 blocks print("Analyzing 3 colored blocks games") for i in range(2,17): for j in range(i+1,17): initialconfig = [1,i,j] game_exists = False for game in games: if initialconfig in game: game_exists = True break if not game_exists: contender = permutations(initialconfig,moves) contender.sort() games.append(contender) print(str(contender[0])+str(len(contender))) if verbose else None print("completed") elif n==4: #4 blocks print("Analyzing 4 colored blocks games") for i in range(2,17): for j in range(i+1,17): for k in range(j+1,17): initialconfig = [1,i,j,k] game_exists = False for game in games: if initialconfig in game: game_exists = True break if not game_exists: contender = permutations(initialconfig,moves) contender.sort() games.append(contender) print(str(contender[0])+str(len(contender))) if verbose else None print("completed") elif n==5: #5 blocks print("Analyzing 5 colored blocks games") for i in range(2,17): for j in range(i+1,17): for k in range(j+1,17): for m in range(k+1,17): if i!=j and j!=k and k!=m and m!=i and m!=j and k!=i: initialconfig = [1,i,j,k,m] game_exists = False for game in games: if initialconfig in game: game_exists = True break if not game_exists: contender = permutations(initialconfig,moves) contender.sort() games.append(contender) print(str(contender[0])+str(len(contender))) if verbose else None print("completed") elif n==6: #6 blocks print("Analyzing 6 colored blocks games") for i in range(2,17): for j in range(i+1,17): for k in range(j+1,17): for m in range(k+1,17): for n in range(m+1,17): initialconfig = [1,i,j,k,m,n] game_exists = False for game in games: if initialconfig in game: game_exists = True break if not game_exists: contender = permutations(initialconfig,moves) contender.sort() games.append(contender) print(str(contender[0])+str(len(contender))) if verbose else None print("completed") elif n==7: #7 blocks print("Analyzing 7 colored blocks games") for i in range(2,17): for j in range(i+1,17): for k in range(j+1,17): for m in range(k+1,17): for n in range(m+1,17): for p in range(n+1,17): initialconfig = [1,i,j,k,m,n,p] game_exists = False for game in games: if initialconfig in game: game_exists = True break if not game_exists: contender = permutations(initialconfig,moves) contender.sort() games.append(contender) print(str(contender[0])+str(len(contender))) if verbose else None print("completed") elif n==8: #8 blocks print("Analyzing 8 colored blocks games") for i in range(2,17): for j in range(i+1,17): for k in range(j+1,17): for m in range(k+1,17): for n in range(m+1,17): for p in range(n+1,17): for q in range(p+1,17): initialconfig = [1,i,j,k,m,n,p,q] game_exists = False for game in games: if initialconfig in game: game_exists = True break if not game_exists: contender = permutations(initialconfig,moves) contender.sort() games.append(contender) print(str(contender[0])+str(len(contender))) if verbose else None print("completed") return(games) def gamelist2(n,moves,verbose=False): #purpose: generates all unique subgames of a defined game #vars: moves the permutations that define the group along with their inverses #limitations: currently works for one colored games only #last updated: 2015-12-09 #notes: this function is necessarily long, instead of condensing the code it is purposely left long for readability and understanding games = [] if n==2: #2 blocks print("Analyzing 2 two-colored blocks games") for i in range(2,17): contender = permutations2([[1],[i]],moves) contender.sort() if contender not in games: games.append(contender) print(str(contender[0])+str(len(contender))) if verbose else None print("completed") elif n==3: print("Analyzing 3 two-colored blocks games") for i in range(1,2): for j in range(i+1,17): for k in range(j+1,17): initialconfig = [[i],[j,k]] game_exists = False for game in games: if initialconfig in game: game_exists = True break if not game_exists: contender = permutations2(initialconfig,moves) contender.sort() games.append(contender) print(str(contender[0])+str(len(contender))) if verbose else None print("completed") elif n==4: print("Analyzing 4 two-colored blocks games") for i in range(1,2): for j in range(i+1,17): for k in range(j+1,17): for m in range(k+1,17): initialconfig1 = [[i],[j,k,m]] initialconfig2 = [[i,j],[k,m]] game1_exists = False game2_exists = False for game in games: if initialconfig1 in game: game1_exists = True break for game in games: if initialconfig2 in game: game2_exists = True break if not game1_exists: contender = permutations2(initialconfig1,moves) contender.sort() games.append(contender) print(str(contender[0])+str(len(contender))) if verbose else None if not game2_exists: contender = permutations2(initialconfig2,moves) contender.sort() games.append(contender) print(str(contender[0])+str(len(contender))) if verbose else None print("completed") elif n==5: print("Analyzing 5 two-colored blocks games") for i in range(1,2): for j in range(i+1,17): for k in range(j+1,17): for m in range(k+1,17): for n in range(m+1,17): initialconfig1 = [[i],[j,k,m,n]] initialconfig2 = [[i,j],[k,m,n]] game1_exists = False game2_exists = False for game in games: if initialconfig1 in game: game1_exists = True break for game in games: if initialconfig2 in game: game2_exists = True break if not game1_exists: contender = permutations2(initialconfig1,moves) contender.sort() games.append(contender) print(str(contender[0])+str(len(contender))) if verbose else None if not game2_exists: contender = permutations2(initialconfig2,moves) contender.sort() games.append(contender) print(str(contender[0])+str(len(contender))) if verbose else None print("completed") elif n==6: print("Analyzing 6 two-colored blocks games") for i in range(2,17): for j in range(i+1,17): for k in range(j+1,17): for m in range(k+1,17): for n in range(m+1,17): initialconfig1 = [[1],[i,j,k,m,n]] initialconfig2 = [[1,i],[j,k,m,n]] initialconfig3 = [[1,i,j],[k,m,n]] game1_exists = False game2_exists = False game3_exists = False for game in games: if initialconfig1 in game: game1_exists = True break for game in games: if initialconfig2 in game: game2_exists = True break for game in games: if initialconfig3 in game: game3_exists = True break if not game1_exists: contender = permutations2(initialconfig1,moves) contender.sort() games.append(contender) print(str(contender[0])+str(len(contender))) if verbose else None if not game2_exists: contender = permutations2(initialconfig2,moves) contender.sort() games.append(contender) print(str(contender[0])+str(len(contender))) if verbose else None if not game3_exists: contender = permutations2(initialconfig3,moves) contender.sort() games.append(contender) print(str(contender[0])+str(len(contender))) if verbose else None print("completed") elif n==7: print("Analyzing 7 two-colored blocks games") for i in range(2,17): for j in range(i+1,17): for k in range(j+1,17): for m in range(k+1,17): for n in range(m+1,17): for p in range(n+1,17): initialconfig1 = [[1],[i,j,k,m,n,p]] initialconfig2 = [[1,i],[j,k,m,n,p]] initialconfig3 = [[1,i,j],[k,m,n,p]] game1_exists = False game2_exists = False game3_exists = False for game in games: if initialconfig1 in game: game1_exists = True break for game in games: if initialconfig2 in game: game2_exists = True break for game in games: if initialconfig3 in game: game3_exists = True break if not game1_exists: contender = permutations2(initialconfig1,moves) contender.sort() games.append(contender) print(str(contender[0])+str(len(contender))) if verbose else None if not game2_exists: contender = permutations2(initialconfig2,moves) contender.sort() games.append(contender) print(str(contender[0])+str(len(contender))) if verbose else None if not game3_exists: contender = permutations2(initialconfig3,moves) contender.sort() games.append(contender) print(str(contender[0])+str(len(contender))) if verbose else None print("completed") elif n==8: print("Analyzing 8 two-colored blocks games") for i in range(2,17): for j in range(i+1,17): for k in range(j+1,17): for m in range(k+1,17): for n in range(m+1,17): for p in range(n+1,17): for q in range(p+1,17): initialconfig1 = [[1],[i,j,k,m,n,p,q]] initialconfig2 = [[1,i],[j,k,m,n,p,q]] initialconfig3 = [[1,i,j],[k,m,n,p,q]] initialconfig4 = [[1,i,j,k],[m,n,p,q]] game1_exists = False game2_exists = False game3_exists = False game4_exists = False for game in games: if initialconfig1 in game: game1_exists = True break for game in games: if initialconfig2 in game: game2_exists = True break for game in games: if initialconfig3 in game: game3_exists = True break for game in games: if initialconfig4 in game: game4_exists = True break if not game1_exists: contender = permutations2(initialconfig1,moves) contender.sort() games.append(contender) print(str(contender[0])+str(len(contender))) if verbose else None if not game2_exists: contender = permutations2(initialconfig2,moves) contender.sort() games.append(contender) print(str(contender[0])+str(len(contender))) if verbose else None if not game3_exists: contender = permutations2(initialconfig3,moves) contender.sort() games.append(contender) print(str(contender[0])+str(len(contender))) if verbose else None if not game4_exists: contender = permutations2(initialconfig4,moves) contender.sort() games.append(contender) print(str(contender[0])+str(len(contender))) if verbose else None print("completed") #nothing larger necessary return(games) def godsnumber(aList,moves,fname="default-adjacency-matrix",version=1,limit=10,verbose=False): #purpose: calculate the god's number / diameter of a graph #vars: ALIST is the initial configuration of a game written as a list, MOVES are a list of generating permutations, #vars: FNAME is a string filename, VERSION 1 or 2 represents how many colors are in game, LIMIT is a computational boundary #limitations: currently works for only one or two colored games #last updated: 2015-12-05 I = eye(len(aList)) #creates the identity matrix if version==2: #creates adjacency matrix A = adjacencyMatrix2(aList,moves) #multiple color games else: A = adjacencyMatrix(aList,moves) #single color games power = 1 result = I #0-walk while (power 3 ##DESC: For use on single colored games, identifies a set of initial positions/games given the number of colored blocks required and a set of moves #moves = easymoves #moves can be set to easymoves or hardmoves depending on which group you are analyzing #n = 2 #set n to an integer with domain of 2-8 where each n represents the number of single-colored boxes #g = gamelist(n,moves,True) #True turns on verbose and will print results as they are encountered, you can remove it and access date through list that is returned. ####GAMELIST2 ##LAST MODIFIED: 2015-12-05 ##CONDITION: Good, can be expensive when running move C with n > 3 ##DESC: For use on two-colored games, identifies a set of initial positions/games given the number of colored blocks required and a set of moves #moves = easymoves #moves can be set to easymoves or hardmoves depending on which group you are analyzing #n = 2 #set n to an integer with domain of 2-8 where each n represents the total number of colored boxes for example if you have box 4 colored boxes of red and blue then let n=4. this will generate the initial configurations of all subgames where 1 box is blue and 3 are red and 2 boxes are blue and 2 are red. #g = gamelist2(2,moves,True) #True turns on verbose and will print results as they are encountered, you can remove it and access date through list that is returned. ####GODSNUMBER ##LAST MODIFIED: 2014-02-11 ##CONDITION: Good ##PURPOSE: Find single God's number / diameter of graph for one single-colored game ##DESC: Creates an adjacency matrix and then computes God's number, printing to file both the adjacency matrix and final matrix I+A+...+A^k where k is God's number #moves = easymoves #position = [1,2,3] #elements = permutations(position,moves) #use this or permutations2() #fname = "SOMEFILENAME" #used for the name of the adjacency matrices that are saved as TXT files #k = godsnumber(elements,moves,fname,True) #True turns on verbose with updates of each power currently checked or you may remove it.