From b189209d98598bfa9a01d1f9185353d7ec259e2d Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Fri, 1 Nov 2019 12:32:23 +0000 Subject: [PATCH 01/14] Import exitless paths script --- bin/exitless_paths.py | 145 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 bin/exitless_paths.py diff --git a/bin/exitless_paths.py b/bin/exitless_paths.py new file mode 100644 index 0000000..7d95d8b --- /dev/null +++ b/bin/exitless_paths.py @@ -0,0 +1,145 @@ +import itertools as it + +k = 6 + +L = [tuple(p) for p in it.permutations(range(k))] #builds each permutation +idem = {L[i]:i for i in range(len(L))} #associates each permutation with a single number (its index in L) +cycle = {} + +for p in L: #building one-cycles + if idem[p] not in cycle: + y = list(p) + while idem[tuple(y)] not in cycle: + cycle[idem[tuple(y)]] = idem[p] + y.append(y.pop(0)) + +def cycs(L1): #returns one-cycles of each vertex + return frozenset([cycle[p] for p in L1]) + + +#searches exitless paths with at most cutoff one-cycles "skipped" +#(one "skips" a one-cycle by using a 3-edge before fully doing a 2-cycle) +def get(S,E,cutoff): + todo = {} #keeps track of the paths we need to check out, and try to continue further. + #the key corresponds to the current end of the path, and the value of the key is the set of such paths with that end + done = {} #keeps track of current least wastage to get to reach states. + #it's key is also the end of the path, which yeilds another dicitonary + #the second dicitonary is we give a certain wastage, which returns the set of states with that end which are known to have this wastage + + for start,cost in S: #builds todo and done + if cost > cutoff: + continue + end = start.pop(-1) #we always pop off our last vertex, that's where we start when we continue our path + if end not in done: + done[end] = {i:set() for i in range(cutoff+1)} + todo[end] = set() + done[end][cost].add(cycs(start)) + todo[end].add(cycs(start)) + + queue = [] + while todo: + for end in todo: + queue.append((end,todo[end])) + todo = {} + + while queue: + front,options = queue.pop(-1) + for cost0 in done[front]: + ops = options&done[front][cost0] + options -= ops + #it would be nice to discard the part of ops which has already been checked with the specific front but I haven't gotten aroudn to it. + + for (e,cost1) in E: + if cost0+cost1 > cutoff: + continue + tail = add(e,front) + poss = build(ops,cycs(tail[:-1])) + + if poss: + if tail[-1] not in done: + done[tail[-1]] = {i:set() for i in range(cutoff+1)} + done[tail[-1]][cost0+cost1] |= poss + if tail[-1] not in todo: + todo[tail[-1]] = set() + todo[tail[-1]] |= poss + print(sum(len(todo[a]) for a in todo)) + findbests(done) + +def findbests(done): #returns the length of the longest path of each 1-cycle skippage. + best = [0]*100 + for end in done: + for cost in done[end]: + if done[end][cost]: + best[cost] = max(best[cost], max(len(p) for p in done[end][cost])) + for i in range(len(best)): + if best[i]: + print(i, best[i]) + + +def build(S,e): #find the paths in S which don't intersect with e + return {path|e for path in S if not path&e} + +def add(e, front): #we start are path at front, and then move places according to e + out = [front] + for step in e: + out.append(step[out[-1]]) + return out + + +def phi(m): #represents a move as a tuple of indices + return tuple(idem[move(p,m)] for p in L) + + +#m is the indices you move around with your edge +#since we always complete our 1-cycles in exitless paths, the last letter of your permutation will end up as the first letter when we're actually doing this move. +def move(p,m): + x = list(p) + y = [] + for i in m: + y.append(x.pop(i)) + return tuple(x+y) + +def reduce(seq): #converts the rearrangement of genmoves for the move function + out = list(seq) + for a in range(len(seq)): + if a < 0: + continue + for i in range(a): + if -1 < seq[i] < seq[a]: + out[a] -= 1 + return out + +def genmoves(n): #create all n-edges + return [reduce(a) for a in it.permutations(list(range(n-1))+[-1])] + + +tau = phi([0,-1]) #2-edge + +Ts = [([0],k-2)] #how we start our path, before our first 3-edge. ([visited vertices], defined wastage) +for i in range(k-2): + Ts.append((Ts[-1][0]+[tau[Ts[-1][0][-1]]],Ts[-1][1]-1)) + +moves = [] + +for m in genmoves(3): #how we will continue our path, consists of a 3-edge, followed by a number of 2-edges + toadd = [((phi(m),),k-2)] #(tuple of maps, defined wastage), we use the sequence of maps with the "add" function + for _ in range(k-2): + toadd.append((toadd[-1][0]+(tau,),toadd[-1][1]-1)) + if len(cycs(add(toadd[-1][0],0))) < len(toadd[-1][0])+1: #checks if we re-entered a 1-cycle + toadd.pop(-1) + break + print(m, phi(m)[0], len(toadd)) + moves += toadd + +#by the current definition of wastage I have, I'd say a 4-edge has an extra waste of (k-1), as we have double the cost (if we subtract the exprected cost of 2 for these being 2+-edges) +'''for m in genmoves(4): + toadd = [((phi(m),tau),k-3+k-1)] + for _ in range(k-3): + toadd.append((toadd[-1][0]+(tau,),toadd[-1][1]-1)) + if len(cycs(add(toadd[-1][0],0))) < len(toadd[-1][0])+1: + toadd.pop(-1) + break + print(m, phi(m)[0], len(toadd)) + moves += toadd''' + +get(Ts,moves,13) From 3fbfdc1326f0d802365a93e29d908df7d7245409 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Fri, 1 Nov 2019 12:33:15 +0000 Subject: [PATCH 02/14] Convert exitless_paths to Unix line format --- bin/exitless_paths.py | 290 +++++++++++++++++++++--------------------- 1 file changed, 145 insertions(+), 145 deletions(-) diff --git a/bin/exitless_paths.py b/bin/exitless_paths.py index 7d95d8b..c629f36 100644 --- a/bin/exitless_paths.py +++ b/bin/exitless_paths.py @@ -1,145 +1,145 @@ -import itertools as it - -k = 6 - -L = [tuple(p) for p in it.permutations(range(k))] #builds each permutation -idem = {L[i]:i for i in range(len(L))} #associates each permutation with a single number (its index in L) -cycle = {} - -for p in L: #building one-cycles - if idem[p] not in cycle: - y = list(p) - while idem[tuple(y)] not in cycle: - cycle[idem[tuple(y)]] = idem[p] - y.append(y.pop(0)) - -def cycs(L1): #returns one-cycles of each vertex - return frozenset([cycle[p] for p in L1]) - - -#searches exitless paths with at most cutoff one-cycles "skipped" -#(one "skips" a one-cycle by using a 3-edge before fully doing a 2-cycle) -def get(S,E,cutoff): - todo = {} #keeps track of the paths we need to check out, and try to continue further. - #the key corresponds to the current end of the path, and the value of the key is the set of such paths with that end - done = {} #keeps track of current least wastage to get to reach states. - #it's key is also the end of the path, which yeilds another dicitonary - #the second dicitonary is we give a certain wastage, which returns the set of states with that end which are known to have this wastage - - for start,cost in S: #builds todo and done - if cost > cutoff: - continue - end = start.pop(-1) #we always pop off our last vertex, that's where we start when we continue our path - if end not in done: - done[end] = {i:set() for i in range(cutoff+1)} - todo[end] = set() - done[end][cost].add(cycs(start)) - todo[end].add(cycs(start)) - - queue = [] - while todo: - for end in todo: - queue.append((end,todo[end])) - todo = {} - - while queue: - front,options = queue.pop(-1) - for cost0 in done[front]: - ops = options&done[front][cost0] - options -= ops - #it would be nice to discard the part of ops which has already been checked with the specific front but I haven't gotten aroudn to it. - - for (e,cost1) in E: - if cost0+cost1 > cutoff: - continue - tail = add(e,front) - poss = build(ops,cycs(tail[:-1])) - - if poss: - if tail[-1] not in done: - done[tail[-1]] = {i:set() for i in range(cutoff+1)} - done[tail[-1]][cost0+cost1] |= poss - if tail[-1] not in todo: - todo[tail[-1]] = set() - todo[tail[-1]] |= poss - print(sum(len(todo[a]) for a in todo)) - findbests(done) - -def findbests(done): #returns the length of the longest path of each 1-cycle skippage. - best = [0]*100 - for end in done: - for cost in done[end]: - if done[end][cost]: - best[cost] = max(best[cost], max(len(p) for p in done[end][cost])) - for i in range(len(best)): - if best[i]: - print(i, best[i]) - - -def build(S,e): #find the paths in S which don't intersect with e - return {path|e for path in S if not path&e} - -def add(e, front): #we start are path at front, and then move places according to e - out = [front] - for step in e: - out.append(step[out[-1]]) - return out - - -def phi(m): #represents a move as a tuple of indices - return tuple(idem[move(p,m)] for p in L) - - -#m is the indices you move around with your edge -#since we always complete our 1-cycles in exitless paths, the last letter of your permutation will end up as the first letter when we're actually doing this move. -def move(p,m): - x = list(p) - y = [] - for i in m: - y.append(x.pop(i)) - return tuple(x+y) - -def reduce(seq): #converts the rearrangement of genmoves for the move function - out = list(seq) - for a in range(len(seq)): - if a < 0: - continue - for i in range(a): - if -1 < seq[i] < seq[a]: - out[a] -= 1 - return out - -def genmoves(n): #create all n-edges - return [reduce(a) for a in it.permutations(list(range(n-1))+[-1])] - - -tau = phi([0,-1]) #2-edge - -Ts = [([0],k-2)] #how we start our path, before our first 3-edge. ([visited vertices], defined wastage) -for i in range(k-2): - Ts.append((Ts[-1][0]+[tau[Ts[-1][0][-1]]],Ts[-1][1]-1)) - -moves = [] - -for m in genmoves(3): #how we will continue our path, consists of a 3-edge, followed by a number of 2-edges - toadd = [((phi(m),),k-2)] #(tuple of maps, defined wastage), we use the sequence of maps with the "add" function - for _ in range(k-2): - toadd.append((toadd[-1][0]+(tau,),toadd[-1][1]-1)) - if len(cycs(add(toadd[-1][0],0))) < len(toadd[-1][0])+1: #checks if we re-entered a 1-cycle - toadd.pop(-1) - break - print(m, phi(m)[0], len(toadd)) - moves += toadd - -#by the current definition of wastage I have, I'd say a 4-edge has an extra waste of (k-1), as we have double the cost (if we subtract the exprected cost of 2 for these being 2+-edges) -'''for m in genmoves(4): - toadd = [((phi(m),tau),k-3+k-1)] - for _ in range(k-3): - toadd.append((toadd[-1][0]+(tau,),toadd[-1][1]-1)) - if len(cycs(add(toadd[-1][0],0))) < len(toadd[-1][0])+1: - toadd.pop(-1) - break - print(m, phi(m)[0], len(toadd)) - moves += toadd''' - -get(Ts,moves,13) +import itertools as it + +k = 6 + +L = [tuple(p) for p in it.permutations(range(k))] #builds each permutation +idem = {L[i]:i for i in range(len(L))} #associates each permutation with a single number (its index in L) +cycle = {} + +for p in L: #building one-cycles + if idem[p] not in cycle: + y = list(p) + while idem[tuple(y)] not in cycle: + cycle[idem[tuple(y)]] = idem[p] + y.append(y.pop(0)) + +def cycs(L1): #returns one-cycles of each vertex + return frozenset([cycle[p] for p in L1]) + + +#searches exitless paths with at most cutoff one-cycles "skipped" +#(one "skips" a one-cycle by using a 3-edge before fully doing a 2-cycle) +def get(S,E,cutoff): + todo = {} #keeps track of the paths we need to check out, and try to continue further. + #the key corresponds to the current end of the path, and the value of the key is the set of such paths with that end + done = {} #keeps track of current least wastage to get to reach states. + #it's key is also the end of the path, which yeilds another dicitonary + #the second dicitonary is we give a certain wastage, which returns the set of states with that end which are known to have this wastage + + for start,cost in S: #builds todo and done + if cost > cutoff: + continue + end = start.pop(-1) #we always pop off our last vertex, that's where we start when we continue our path + if end not in done: + done[end] = {i:set() for i in range(cutoff+1)} + todo[end] = set() + done[end][cost].add(cycs(start)) + todo[end].add(cycs(start)) + + queue = [] + while todo: + for end in todo: + queue.append((end,todo[end])) + todo = {} + + while queue: + front,options = queue.pop(-1) + for cost0 in done[front]: + ops = options&done[front][cost0] + options -= ops + #it would be nice to discard the part of ops which has already been checked with the specific front but I haven't gotten aroudn to it. + + for (e,cost1) in E: + if cost0+cost1 > cutoff: + continue + tail = add(e,front) + poss = build(ops,cycs(tail[:-1])) + + if poss: + if tail[-1] not in done: + done[tail[-1]] = {i:set() for i in range(cutoff+1)} + done[tail[-1]][cost0+cost1] |= poss + if tail[-1] not in todo: + todo[tail[-1]] = set() + todo[tail[-1]] |= poss + print(sum(len(todo[a]) for a in todo)) + findbests(done) + +def findbests(done): #returns the length of the longest path of each 1-cycle skippage. + best = [0]*100 + for end in done: + for cost in done[end]: + if done[end][cost]: + best[cost] = max(best[cost], max(len(p) for p in done[end][cost])) + for i in range(len(best)): + if best[i]: + print(i, best[i]) + + +def build(S,e): #find the paths in S which don't intersect with e + return {path|e for path in S if not path&e} + +def add(e, front): #we start are path at front, and then move places according to e + out = [front] + for step in e: + out.append(step[out[-1]]) + return out + + +def phi(m): #represents a move as a tuple of indices + return tuple(idem[move(p,m)] for p in L) + + +#m is the indices you move around with your edge +#since we always complete our 1-cycles in exitless paths, the last letter of your permutation will end up as the first letter when we're actually doing this move. +def move(p,m): + x = list(p) + y = [] + for i in m: + y.append(x.pop(i)) + return tuple(x+y) + +def reduce(seq): #converts the rearrangement of genmoves for the move function + out = list(seq) + for a in range(len(seq)): + if a < 0: + continue + for i in range(a): + if -1 < seq[i] < seq[a]: + out[a] -= 1 + return out + +def genmoves(n): #create all n-edges + return [reduce(a) for a in it.permutations(list(range(n-1))+[-1])] + + +tau = phi([0,-1]) #2-edge + +Ts = [([0],k-2)] #how we start our path, before our first 3-edge. ([visited vertices], defined wastage) +for i in range(k-2): + Ts.append((Ts[-1][0]+[tau[Ts[-1][0][-1]]],Ts[-1][1]-1)) + +moves = [] + +for m in genmoves(3): #how we will continue our path, consists of a 3-edge, followed by a number of 2-edges + toadd = [((phi(m),),k-2)] #(tuple of maps, defined wastage), we use the sequence of maps with the "add" function + for _ in range(k-2): + toadd.append((toadd[-1][0]+(tau,),toadd[-1][1]-1)) + if len(cycs(add(toadd[-1][0],0))) < len(toadd[-1][0])+1: #checks if we re-entered a 1-cycle + toadd.pop(-1) + break + print(m, phi(m)[0], len(toadd)) + moves += toadd + +#by the current definition of wastage I have, I'd say a 4-edge has an extra waste of (k-1), as we have double the cost (if we subtract the exprected cost of 2 for these being 2+-edges) +'''for m in genmoves(4): + toadd = [((phi(m),tau),k-3+k-1)] + for _ in range(k-3): + toadd.append((toadd[-1][0]+(tau,),toadd[-1][1]-1)) + if len(cycs(add(toadd[-1][0],0))) < len(toadd[-1][0])+1: + toadd.pop(-1) + break + print(m, phi(m)[0], len(toadd)) + moves += toadd''' + +get(Ts,moves,13) From 3794b9a3ec90c3f74167a1a9d82ead47fca734e2 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Fri, 1 Nov 2019 12:34:26 +0000 Subject: [PATCH 03/14] Make exitless_paths.py executable --- bin/exitless_paths.py | 2 ++ 1 file changed, 2 insertions(+) mode change 100644 => 100755 bin/exitless_paths.py diff --git a/bin/exitless_paths.py b/bin/exitless_paths.py old mode 100644 new mode 100755 index c629f36..ea5a642 --- a/bin/exitless_paths.py +++ b/bin/exitless_paths.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + import itertools as it k = 6 From 09f1a50cd5ba683b4beee157fdf62c67c7f11323 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Fri, 1 Nov 2019 12:35:36 +0000 Subject: [PATCH 04/14] Delete spurious whitespace --- bin/exitless_paths.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bin/exitless_paths.py b/bin/exitless_paths.py index ea5a642..b201d74 100755 --- a/bin/exitless_paths.py +++ b/bin/exitless_paths.py @@ -37,7 +37,7 @@ def get(S,E,cutoff): todo[end] = set() done[end][cost].add(cycs(start)) todo[end].add(cycs(start)) - + queue = [] while todo: for end in todo: @@ -56,7 +56,7 @@ def get(S,E,cutoff): continue tail = add(e,front) poss = build(ops,cycs(tail[:-1])) - + if poss: if tail[-1] not in done: done[tail[-1]] = {i:set() for i in range(cutoff+1)} @@ -76,7 +76,7 @@ def findbests(done): #returns the length of the longest path of each 1-cycle ski for i in range(len(best)): if best[i]: print(i, best[i]) - + def build(S,e): #find the paths in S which don't intersect with e return {path|e for path in S if not path&e} @@ -132,7 +132,7 @@ def genmoves(n): #create all n-edges break print(m, phi(m)[0], len(toadd)) moves += toadd - + #by the current definition of wastage I have, I'd say a 4-edge has an extra waste of (k-1), as we have double the cost (if we subtract the exprected cost of 2 for these being 2+-edges) '''for m in genmoves(4): toadd = [((phi(m),tau),k-3+k-1)] From be273db547711f59d805f542bfcd70b5e648422a Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Fri, 1 Nov 2019 12:37:12 +0000 Subject: [PATCH 05/14] Set k=3 for faster debugging cycles k=6 was causing my machine to lock up :-( --- bin/exitless_paths.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/exitless_paths.py b/bin/exitless_paths.py index b201d74..72733a7 100755 --- a/bin/exitless_paths.py +++ b/bin/exitless_paths.py @@ -2,7 +2,7 @@ import itertools as it -k = 6 +k = 3 L = [tuple(p) for p in it.permutations(range(k))] #builds each permutation idem = {L[i]:i for i in range(len(L))} #associates each permutation with a single number (its index in L) From d22e1c73c1df8f1e0e7e77b1967025ea8fbd23e0 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Fri, 1 Nov 2019 12:38:48 +0000 Subject: [PATCH 06/14] Allow importing file without running it --- bin/exitless_paths.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/exitless_paths.py b/bin/exitless_paths.py index 72733a7..87959c3 100755 --- a/bin/exitless_paths.py +++ b/bin/exitless_paths.py @@ -144,4 +144,5 @@ def genmoves(n): #create all n-edges print(m, phi(m)[0], len(toadd)) moves += toadd''' -get(Ts,moves,13) +if __name__ == '__main__': + get(Ts,moves,13) From b2279a1ae8ba98fe16b5d6e2b8144b60525a2d41 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Fri, 1 Nov 2019 12:41:29 +0000 Subject: [PATCH 07/14] Add whitespace after commas --- bin/exitless_paths.py | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/bin/exitless_paths.py b/bin/exitless_paths.py index 87959c3..214352b 100755 --- a/bin/exitless_paths.py +++ b/bin/exitless_paths.py @@ -21,14 +21,14 @@ def cycs(L1): #returns one-cycles of each vertex #searches exitless paths with at most cutoff one-cycles "skipped" #(one "skips" a one-cycle by using a 3-edge before fully doing a 2-cycle) -def get(S,E,cutoff): +def get(S, E, cutoff): todo = {} #keeps track of the paths we need to check out, and try to continue further. #the key corresponds to the current end of the path, and the value of the key is the set of such paths with that end done = {} #keeps track of current least wastage to get to reach states. #it's key is also the end of the path, which yeilds another dicitonary #the second dicitonary is we give a certain wastage, which returns the set of states with that end which are known to have this wastage - for start,cost in S: #builds todo and done + for start, cost in S: #builds todo and done if cost > cutoff: continue end = start.pop(-1) #we always pop off our last vertex, that's where we start when we continue our path @@ -41,21 +41,21 @@ def get(S,E,cutoff): queue = [] while todo: for end in todo: - queue.append((end,todo[end])) + queue.append((end, todo[end])) todo = {} while queue: - front,options = queue.pop(-1) + front, options = queue.pop(-1) for cost0 in done[front]: ops = options&done[front][cost0] options -= ops #it would be nice to discard the part of ops which has already been checked with the specific front but I haven't gotten aroudn to it. - for (e,cost1) in E: + for (e, cost1) in E: if cost0+cost1 > cutoff: continue - tail = add(e,front) - poss = build(ops,cycs(tail[:-1])) + tail = add(e, front) + poss = build(ops, cycs(tail[:-1])) if poss: if tail[-1] not in done: @@ -78,7 +78,7 @@ def findbests(done): #returns the length of the longest path of each 1-cycle ski print(i, best[i]) -def build(S,e): #find the paths in S which don't intersect with e +def build(S, e): #find the paths in S which don't intersect with e return {path|e for path in S if not path&e} def add(e, front): #we start are path at front, and then move places according to e @@ -89,12 +89,12 @@ def add(e, front): #we start are path at front, and then move places according t def phi(m): #represents a move as a tuple of indices - return tuple(idem[move(p,m)] for p in L) + return tuple(idem[move(p, m)] for p in L) #m is the indices you move around with your edge #since we always complete our 1-cycles in exitless paths, the last letter of your permutation will end up as the first letter when we're actually doing this move. -def move(p,m): +def move(p, m): x = list(p) y = [] for i in m: @@ -115,19 +115,19 @@ def genmoves(n): #create all n-edges return [reduce(a) for a in it.permutations(list(range(n-1))+[-1])] -tau = phi([0,-1]) #2-edge +tau = phi([0, -1]) #2-edge -Ts = [([0],k-2)] #how we start our path, before our first 3-edge. ([visited vertices], defined wastage) +Ts = [([0], k-2)] #how we start our path, before our first 3-edge. ([visited vertices], defined wastage) for i in range(k-2): - Ts.append((Ts[-1][0]+[tau[Ts[-1][0][-1]]],Ts[-1][1]-1)) + Ts.append((Ts[-1][0]+[tau[Ts[-1][0][-1]]], Ts[-1][1]-1)) moves = [] for m in genmoves(3): #how we will continue our path, consists of a 3-edge, followed by a number of 2-edges - toadd = [((phi(m),),k-2)] #(tuple of maps, defined wastage), we use the sequence of maps with the "add" function + toadd = [((phi(m),), k-2)] #(tuple of maps, defined wastage), we use the sequence of maps with the "add" function for _ in range(k-2): - toadd.append((toadd[-1][0]+(tau,),toadd[-1][1]-1)) - if len(cycs(add(toadd[-1][0],0))) < len(toadd[-1][0])+1: #checks if we re-entered a 1-cycle + toadd.append((toadd[-1][0]+(tau,), toadd[-1][1]-1)) + if len(cycs(add(toadd[-1][0], 0))) < len(toadd[-1][0])+1: #checks if we re-entered a 1-cycle toadd.pop(-1) break print(m, phi(m)[0], len(toadd)) @@ -135,14 +135,14 @@ def genmoves(n): #create all n-edges #by the current definition of wastage I have, I'd say a 4-edge has an extra waste of (k-1), as we have double the cost (if we subtract the exprected cost of 2 for these being 2+-edges) '''for m in genmoves(4): - toadd = [((phi(m),tau),k-3+k-1)] + toadd = [((phi(m), tau), k-3+k-1)] for _ in range(k-3): - toadd.append((toadd[-1][0]+(tau,),toadd[-1][1]-1)) - if len(cycs(add(toadd[-1][0],0))) < len(toadd[-1][0])+1: + toadd.append((toadd[-1][0]+(tau,), toadd[-1][1]-1)) + if len(cycs(add(toadd[-1][0], 0))) < len(toadd[-1][0])+1: toadd.pop(-1) break print(m, phi(m)[0], len(toadd)) moves += toadd''' if __name__ == '__main__': - get(Ts,moves,13) + get(Ts, moves, 13) From 6856940b60a960046716bc071dc790ebb6ad3355 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Fri, 1 Nov 2019 12:45:38 +0000 Subject: [PATCH 08/14] PEP8: spaces after #s --- bin/exitless_paths.py | 56 +++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/bin/exitless_paths.py b/bin/exitless_paths.py index 214352b..56c3b57 100755 --- a/bin/exitless_paths.py +++ b/bin/exitless_paths.py @@ -4,34 +4,34 @@ k = 3 -L = [tuple(p) for p in it.permutations(range(k))] #builds each permutation -idem = {L[i]:i for i in range(len(L))} #associates each permutation with a single number (its index in L) +L = [tuple(p) for p in it.permutations(range(k))] # builds each permutation +idem = {L[i]:i for i in range(len(L))} # associates each permutation with a single number (its index in L) cycle = {} -for p in L: #building one-cycles +for p in L: # building one-cycles if idem[p] not in cycle: y = list(p) while idem[tuple(y)] not in cycle: cycle[idem[tuple(y)]] = idem[p] y.append(y.pop(0)) -def cycs(L1): #returns one-cycles of each vertex +def cycs(L1): # returns one-cycles of each vertex return frozenset([cycle[p] for p in L1]) -#searches exitless paths with at most cutoff one-cycles "skipped" -#(one "skips" a one-cycle by using a 3-edge before fully doing a 2-cycle) +# searches exitless paths with at most cutoff one-cycles "skipped" +# (one "skips" a one-cycle by using a 3-edge before fully doing a 2-cycle) def get(S, E, cutoff): - todo = {} #keeps track of the paths we need to check out, and try to continue further. - #the key corresponds to the current end of the path, and the value of the key is the set of such paths with that end - done = {} #keeps track of current least wastage to get to reach states. - #it's key is also the end of the path, which yeilds another dicitonary - #the second dicitonary is we give a certain wastage, which returns the set of states with that end which are known to have this wastage + todo = {} # keeps track of the paths we need to check out, and try to continue further. + # the key corresponds to the current end of the path, and the value of the key is the set of such paths with that end + done = {} # keeps track of current least wastage to get to reach states. + # it's key is also the end of the path, which yeilds another dicitonary + # the second dicitonary is we give a certain wastage, which returns the set of states with that end which are known to have this wastage - for start, cost in S: #builds todo and done + for start, cost in S: # builds todo and done if cost > cutoff: continue - end = start.pop(-1) #we always pop off our last vertex, that's where we start when we continue our path + end = start.pop(-1) # we always pop off our last vertex, that's where we start when we continue our path if end not in done: done[end] = {i:set() for i in range(cutoff+1)} todo[end] = set() @@ -49,7 +49,7 @@ def get(S, E, cutoff): for cost0 in done[front]: ops = options&done[front][cost0] options -= ops - #it would be nice to discard the part of ops which has already been checked with the specific front but I haven't gotten aroudn to it. + # it would be nice to discard the part of ops which has already been checked with the specific front but I haven't gotten aroudn to it. for (e, cost1) in E: if cost0+cost1 > cutoff: @@ -67,7 +67,7 @@ def get(S, E, cutoff): print(sum(len(todo[a]) for a in todo)) findbests(done) -def findbests(done): #returns the length of the longest path of each 1-cycle skippage. +def findbests(done): # returns the length of the longest path of each 1-cycle skippage. best = [0]*100 for end in done: for cost in done[end]: @@ -78,22 +78,22 @@ def findbests(done): #returns the length of the longest path of each 1-cycle ski print(i, best[i]) -def build(S, e): #find the paths in S which don't intersect with e +def build(S, e): # find the paths in S which don't intersect with e return {path|e for path in S if not path&e} -def add(e, front): #we start are path at front, and then move places according to e +def add(e, front): # we start are path at front, and then move places according to e out = [front] for step in e: out.append(step[out[-1]]) return out -def phi(m): #represents a move as a tuple of indices +def phi(m): # represents a move as a tuple of indices return tuple(idem[move(p, m)] for p in L) -#m is the indices you move around with your edge -#since we always complete our 1-cycles in exitless paths, the last letter of your permutation will end up as the first letter when we're actually doing this move. +# m is the indices you move around with your edge +# since we always complete our 1-cycles in exitless paths, the last letter of your permutation will end up as the first letter when we're actually doing this move. def move(p, m): x = list(p) y = [] @@ -101,7 +101,7 @@ def move(p, m): y.append(x.pop(i)) return tuple(x+y) -def reduce(seq): #converts the rearrangement of genmoves for the move function +def reduce(seq): # converts the rearrangement of genmoves for the move function out = list(seq) for a in range(len(seq)): if a < 0: @@ -111,29 +111,29 @@ def reduce(seq): #converts the rearrangement of genmoves for the move function out[a] -= 1 return out -def genmoves(n): #create all n-edges +def genmoves(n): # create all n-edges return [reduce(a) for a in it.permutations(list(range(n-1))+[-1])] -tau = phi([0, -1]) #2-edge +tau = phi([0, -1]) # 2-edge -Ts = [([0], k-2)] #how we start our path, before our first 3-edge. ([visited vertices], defined wastage) +Ts = [([0], k-2)] # how we start our path, before our first 3-edge. ([visited vertices], defined wastage) for i in range(k-2): Ts.append((Ts[-1][0]+[tau[Ts[-1][0][-1]]], Ts[-1][1]-1)) moves = [] -for m in genmoves(3): #how we will continue our path, consists of a 3-edge, followed by a number of 2-edges - toadd = [((phi(m),), k-2)] #(tuple of maps, defined wastage), we use the sequence of maps with the "add" function +for m in genmoves(3): # how we will continue our path, consists of a 3-edge, followed by a number of 2-edges + toadd = [((phi(m),), k-2)] # (tuple of maps, defined wastage), we use the sequence of maps with the "add" function for _ in range(k-2): toadd.append((toadd[-1][0]+(tau,), toadd[-1][1]-1)) - if len(cycs(add(toadd[-1][0], 0))) < len(toadd[-1][0])+1: #checks if we re-entered a 1-cycle + if len(cycs(add(toadd[-1][0], 0))) < len(toadd[-1][0])+1: # checks if we re-entered a 1-cycle toadd.pop(-1) break print(m, phi(m)[0], len(toadd)) moves += toadd -#by the current definition of wastage I have, I'd say a 4-edge has an extra waste of (k-1), as we have double the cost (if we subtract the exprected cost of 2 for these being 2+-edges) +# by the current definition of wastage I have, I'd say a 4-edge has an extra waste of (k-1), as we have double the cost (if we subtract the exprected cost of 2 for these being 2+-edges) '''for m in genmoves(4): toadd = [((phi(m), tau), k-3+k-1)] for _ in range(k-3): From 8166fc2bec0bb800242de6c3cabe2fa4c45c70d9 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Fri, 1 Nov 2019 15:17:24 +0000 Subject: [PATCH 09/14] PEP8: wrap long lines --- bin/exitless_paths.py | 67 ++++++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 23 deletions(-) diff --git a/bin/exitless_paths.py b/bin/exitless_paths.py index 56c3b57..332cbc5 100755 --- a/bin/exitless_paths.py +++ b/bin/exitless_paths.py @@ -5,7 +5,8 @@ k = 3 L = [tuple(p) for p in it.permutations(range(k))] # builds each permutation -idem = {L[i]:i for i in range(len(L))} # associates each permutation with a single number (its index in L) + # associates each permutation with a single number (its index in L) +idem = {L[i]:i for i in range(len(L))} cycle = {} for p in L: # building one-cycles @@ -19,19 +20,24 @@ def cycs(L1): # returns one-cycles of each vertex return frozenset([cycle[p] for p in L1]) -# searches exitless paths with at most cutoff one-cycles "skipped" -# (one "skips" a one-cycle by using a 3-edge before fully doing a 2-cycle) def get(S, E, cutoff): - todo = {} # keeps track of the paths we need to check out, and try to continue further. - # the key corresponds to the current end of the path, and the value of the key is the set of such paths with that end - done = {} # keeps track of current least wastage to get to reach states. - # it's key is also the end of the path, which yeilds another dicitonary - # the second dicitonary is we give a certain wastage, which returns the set of states with that end which are known to have this wastage - + """searches exitless paths with at most cutoff one-cycles "skipped" + (one "skips" a one-cycle by using a 3-edge before fully doing a 2-cycle)""" + # keeps track of the paths we need to check out, and try to continue + # further. The key corresponds to the current end of the path, and the + # value of the key is the set of such paths with that end + todo = {} + # keeps track of current least wastage to get to reach states. Its key is + # also the end of the path, which yeilds another dictonary. + # The second dictionary is we give a certain wastage, which returns the set + # of states with that end which are known to have this wastage + done = {} for start, cost in S: # builds todo and done if cost > cutoff: continue - end = start.pop(-1) # we always pop off our last vertex, that's where we start when we continue our path + # we always pop off our last vertex, that's where we start when we + # continue our path + end = start.pop(-1) if end not in done: done[end] = {i:set() for i in range(cutoff+1)} todo[end] = set() @@ -40,7 +46,7 @@ def get(S, E, cutoff): queue = [] while todo: - for end in todo: + for end in todo: queue.append((end, todo[end])) todo = {} @@ -49,7 +55,9 @@ def get(S, E, cutoff): for cost0 in done[front]: ops = options&done[front][cost0] options -= ops - # it would be nice to discard the part of ops which has already been checked with the specific front but I haven't gotten aroudn to it. + # it would be nice to discard the part of ops which has already + # been checked with the specific front but I haven't gotten + # around to it. for (e, cost1) in E: if cost0+cost1 > cutoff: @@ -67,7 +75,8 @@ def get(S, E, cutoff): print(sum(len(todo[a]) for a in todo)) findbests(done) -def findbests(done): # returns the length of the longest path of each 1-cycle skippage. +def findbests(done): + """returns the length of the longest path of each 1-cycle skippage.""" best = [0]*100 for end in done: for cost in done[end]: @@ -81,7 +90,8 @@ def findbests(done): # returns the length of the longest path of each 1-cycle sk def build(S, e): # find the paths in S which don't intersect with e return {path|e for path in S if not path&e} -def add(e, front): # we start are path at front, and then move places according to e +def add(e, front): + """we start our path at front, and then move places according to e""" out = [front] for step in e: out.append(step[out[-1]]) @@ -92,8 +102,9 @@ def phi(m): # represents a move as a tuple of indices return tuple(idem[move(p, m)] for p in L) -# m is the indices you move around with your edge -# since we always complete our 1-cycles in exitless paths, the last letter of your permutation will end up as the first letter when we're actually doing this move. +# m is the indices you move around with your edge since we always complete our +# 1-cycles in exitless paths, the last letter of your permutation will end up +# as the first letter when we're actually doing this move. def move(p, m): x = list(p) y = [] @@ -101,7 +112,8 @@ def move(p, m): y.append(x.pop(i)) return tuple(x+y) -def reduce(seq): # converts the rearrangement of genmoves for the move function +def reduce(seq): + """converts the rearrangement of genmoves for the move function""" out = list(seq) for a in range(len(seq)): if a < 0: @@ -111,29 +123,38 @@ def reduce(seq): # converts the rearrangement of genmoves for the move function out[a] -= 1 return out -def genmoves(n): # create all n-edges +def genmoves(n): + """create all n-edges""" return [reduce(a) for a in it.permutations(list(range(n-1))+[-1])] tau = phi([0, -1]) # 2-edge -Ts = [([0], k-2)] # how we start our path, before our first 3-edge. ([visited vertices], defined wastage) + # how we start our path, before our first 3-edge. ([visited vertices], defined + # wastage) +Ts = [([0], k-2)] for i in range(k-2): Ts.append((Ts[-1][0]+[tau[Ts[-1][0][-1]]], Ts[-1][1]-1)) moves = [] -for m in genmoves(3): # how we will continue our path, consists of a 3-edge, followed by a number of 2-edges - toadd = [((phi(m),), k-2)] # (tuple of maps, defined wastage), we use the sequence of maps with the "add" function + # how we will continue our path? Consists of a 3-edge, followed by a number of + # 2-edges +for m in genmoves(3): + toadd = [((phi(m),), k-2)] # (tuple of maps, defined wastage) + # we use the sequence of maps with the "add" function for _ in range(k-2): toadd.append((toadd[-1][0]+(tau,), toadd[-1][1]-1)) - if len(cycs(add(toadd[-1][0], 0))) < len(toadd[-1][0])+1: # checks if we re-entered a 1-cycle + # checks if we re-entered a 1-cycle + if len(cycs(add(toadd[-1][0], 0))) < len(toadd[-1][0])+1: toadd.pop(-1) break print(m, phi(m)[0], len(toadd)) moves += toadd -# by the current definition of wastage I have, I'd say a 4-edge has an extra waste of (k-1), as we have double the cost (if we subtract the exprected cost of 2 for these being 2+-edges) +# by the current definition of wastage I have, I'd say a 4-edge has an extra +# waste of (k-1), as we have double the cost (if we subtract the exprected cost +# of 2 for these being 2+-edges) '''for m in genmoves(4): toadd = [((phi(m), tau), k-3+k-1)] for _ in range(k-3): From 48fa70db694f919d701500853b4efba14cc22d22 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Fri, 1 Nov 2019 15:18:31 +0000 Subject: [PATCH 10/14] PEP8: replace range/len with enumerate --- bin/exitless_paths.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/exitless_paths.py b/bin/exitless_paths.py index 332cbc5..84d4ea1 100755 --- a/bin/exitless_paths.py +++ b/bin/exitless_paths.py @@ -82,7 +82,7 @@ def findbests(done): for cost in done[end]: if done[end][cost]: best[cost] = max(best[cost], max(len(p) for p in done[end][cost])) - for i in range(len(best)): + for i, b in enumerate(best): if best[i]: print(i, best[i]) From 5b5f8a1884dacb638437274889d5915f2ef7b4f5 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Fri, 1 Nov 2019 15:46:42 +0000 Subject: [PATCH 11/14] Set k to 5 so I have something to profile --- bin/exitless_paths.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/exitless_paths.py b/bin/exitless_paths.py index 84d4ea1..c944626 100755 --- a/bin/exitless_paths.py +++ b/bin/exitless_paths.py @@ -2,7 +2,7 @@ import itertools as it -k = 3 +k = 5 L = [tuple(p) for p in it.permutations(range(k))] # builds each permutation # associates each permutation with a single number (its index in L) From 87a5f14f535eacd2922d00ab1dbb5e5aa211b477 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Fri, 1 Nov 2019 16:15:14 +0000 Subject: [PATCH 12/14] Print summary of memory usage --- bin/exitless_paths.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bin/exitless_paths.py b/bin/exitless_paths.py index c944626..347733b 100755 --- a/bin/exitless_paths.py +++ b/bin/exitless_paths.py @@ -2,6 +2,8 @@ import itertools as it +from pympler import muppy, summary + k = 5 L = [tuple(p) for p in it.permutations(range(k))] # builds each permutation @@ -167,3 +169,6 @@ def genmoves(n): if __name__ == '__main__': get(Ts, moves, 13) + all_objects = muppy.get_objects() + sum1 = summary.summarize(all_objects) + summary.print_(sum1) From e51333d810c6826a2f15ccdfdf2935e5c78796e2 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Fri, 1 Nov 2019 16:26:46 +0000 Subject: [PATCH 13/14] Output memory usage after each main iteration --- bin/exitless_paths.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/exitless_paths.py b/bin/exitless_paths.py index 347733b..b8f3908 100755 --- a/bin/exitless_paths.py +++ b/bin/exitless_paths.py @@ -75,6 +75,9 @@ def get(S, E, cutoff): todo[tail[-1]] = set() todo[tail[-1]] |= poss print(sum(len(todo[a]) for a in todo)) + all_objects = muppy.get_objects() + sum1 = summary.summarize(all_objects) + summary.print_(sum1) findbests(done) def findbests(done): @@ -169,6 +172,3 @@ def genmoves(n): if __name__ == '__main__': get(Ts, moves, 13) - all_objects = muppy.get_objects() - sum1 = summary.summarize(all_objects) - summary.print_(sum1) From ae84d7cb8c52e9556a1106a2b69d615c4e63fe81 Mon Sep 17 00:00:00 2001 From: Miles Gould Date: Fri, 1 Nov 2019 16:37:09 +0000 Subject: [PATCH 14/14] Use bitsets instead of frozensets --- bin/exitless_paths.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bin/exitless_paths.py b/bin/exitless_paths.py index b8f3908..4a24278 100755 --- a/bin/exitless_paths.py +++ b/bin/exitless_paths.py @@ -2,6 +2,7 @@ import itertools as it +from bitsets import bitset from pympler import muppy, summary k = 5 @@ -18,8 +19,10 @@ cycle[idem[tuple(y)]] = idem[p] y.append(y.pop(0)) +Cycles = bitset('Cycles', tuple(set(cycle.values()))) + def cycs(L1): # returns one-cycles of each vertex - return frozenset([cycle[p] for p in L1]) + return Cycles([cycle[p] for p in L1]) def get(S, E, cutoff):