Commit b005c4be authored by Hrishee Shastri's avatar Hrishee Shastri
Browse files

Merge branch 'refactor-gdc' into 'release'

Refactor gdc

See merge request eschoute/reversal-sort!3
parents 5b292738 72aa1518
......@@ -3,7 +3,9 @@ from itertools import permutations
from math import factorial
import random
from reversal_sort import SBR, adaptive_tbs, tripartite_binary_sort
from reversal_sort import routing
from reversal_sort.tripartite_binary_sort import tripartite_binary_sort
from reversal_sort.adaptive_tbs import adaptive_tb_sort
"""
Collects routing time data for the algorithms GDC(TBS), GDC(ATBS), and OES.
......@@ -117,7 +119,7 @@ def new_rand_perms_file(alglist, algnames, nlist, count):
def main(NUM_PERMS_PER_LENGTH, LENGTH_FROM, LENGTH_TO):
algnames = ['OES', 'GDC(TBS)', 'GDC(ATBS)']
algs = [SBR.odd_even_sort, tripartite_binary_sort.GDC_TBS, adaptive_tbs.GDC_ATBS]
algs = [routing.odd_even_sort, routing.GDC_TBS, routing.GDC_ATBS]
new_rand_perms_file(algs, algnames, list(range(LENGTH_FROM,LENGTH_TO + 1)), NUM_PERMS_PER_LENGTH)
......@@ -127,7 +129,8 @@ if __name__ == "__main__":
for line in sys.stdin:
perm = [int(el) for el in line.split()]
# Check if is permutation
if set(range(0, len(perm))) != set(perm):
if set(range(len(perm))) != set(perm):
raise ValueError(f"Given permutation does not contain all elements in [0,{len(perm)-1}].")
reversals = tripartite_binary_sort.routing_divideconquer_tbs(perm)
router = routing.DCRoute(adaptive_tb_sort)
reversals = router.route(perm)
print(reversals) # TODO: Implement pretty print
\ No newline at end of file
import math
from . import reversal
def GDC_ATBS(perm):
"""
Sorts the given permutation by using a version of Tripartite Binary Sort using dynamic programming. Returns
the cost of sorting the permutation.
"""
revs = sequential_permutation_sort_parallelized(perm, 0, len(perm) - 1)
return reversal.compress_reversals(revs, len(perm)) / 3
from .reversal import Reversal
def beginning_index_of_weight(w, v, i):
"""
......@@ -64,19 +54,7 @@ def weights_to_cumulative(w):
v.append(oddsum)
return v
def perm_to_01(L, i, j):
"""
TUrns a permutation L[i:j] into a permutation of 0s and 1s, where L[i] is 0 if it is
less than the median and L[i] is 1 if it is greater than the median
"""
subseq = L[i:j + 1]
sorted_subseq = sorted(subseq)
median = (j - i) // 2
return L[:i] + [int(sorted_subseq.index(k) > median) for k in subseq] + L[j + 1:]
def binary_sort_parallel(b):
def adaptive_tb_sort(b):
"""
Returns the reversals required to sort the given binary sequence using a parallelized version of the sequential
binary sort algorithm found in Bender et al.'s paper "Improved Bounds on Sorting by Length-Weighted Reversals".
......@@ -95,7 +73,6 @@ def binary_sort_parallel(b):
fill_4_parallel(w, v, A, i, j, (i + 1) % 2)
return A[1][len(w) - 2][0][1]
def fill_4_parallel(w, v, A, i, j, b):
"""
Fills the matrix A[i,j,b] according to equation (4) in the paper. The parallelizations are made in equation (6).
......@@ -105,7 +82,7 @@ def fill_4_parallel(w, v, A, i, j, b):
elif j == i + 1 and b == (i + 1) % 2:
leftind = beginning_index_of_weight(w, v, i)
rightind = end_index_of_weight(w, v, j)
r = reversal.Reversal(leftind, rightind)
r = Reversal(leftind, rightind)
A[i][j][b] = [w[i] + w[j] + 1, [r]]
elif j > i + 1 and b == i % 2:
A[i][j][b] = A[i + 1][j][b]
......@@ -130,23 +107,6 @@ def fill_6_parallel(w, v, A, i, j, b):
minval = val
leftind = beginning_index_of_weight(w, v, t + 1) - (v[t - ((t - i) % 2)] - v[i] + w[i])
rightind = end_index_of_weight(w, v, k - 1) + (v[j] - v[k + ((j - k) % 2)] + w[k + ((j - k) % 2)])
r = reversal.Reversal(leftind, rightind)
r = Reversal(leftind, rightind)
minrevs = A[i][t][b][1] + A[t + 1][k - 1][1 - b][1] + A[k][j][b][1] + [r]
A[i][j][b] = [minval, minrevs]
def sequential_permutation_sort_parallelized(perm, i, j):
"""
Sorts the given permutation from index i to j (inclusive) by using a parallelized version of the optimal sequential
sorting search for 0/1 strings. The method of parallelization is dictated by the version number. Returns a list of
reversals used to sort the permutation.
"""
if i == j:
return []
b = perm_to_01(perm, i, j)
revs = binary_sort_parallel(b[i:j + 1])
offsetrevs = [reversal.Reversal(i + r.beg, i + r.end) for r in revs]
m = (i + j) // 2
reversal.apply_revs(offsetrevs, perm)
return offsetrevs + sequential_permutation_sort_parallelized(perm, i, m) + \
sequential_permutation_sort_parallelized(perm, m + 1, j)
\ No newline at end of file
......@@ -32,6 +32,15 @@ class Reversal:
def get_length(self):
return abs(self.beg - self.end) + 1
def offset(self, by):
return Reversal(self.beg + by, self.end + by)
def apply(self, perm):
# Perform the given reversal on the given permutation
i = self.beg
j = self.end
perm[i:j+1] = perm[i:j+1][::-1]
def __lt__(self, other):
return self.beg < other.beg
......@@ -42,13 +51,6 @@ class Reversal:
return repr(self)
# Perform the given reversal on the given permutation
def reverse(permutation, reversal):
i = reversal.beg
j = reversal.end
permutation[i:j+1] = permutation[i:j+1][::-1]
# Determines if the reversals in the given list are independent, where perm_length is the length of the permutation
def independent(rev_list, perm_length):
busy = [0]*perm_length
......@@ -186,48 +188,6 @@ class ReversalCompresser:
return self.counter
def odd_even_sort(L):
"""
L: List to be sorted. Since we can reduce the problem of sorting according to a permutation
pi to just sorting according to the identity permutation, we just sort normally.
Odd even sort is performed in parallel. Each pass is an odd pass or even pass, where we look
at every odd/even pair and swap if they are out of order. Each pass takes time 1, since there are
n/2 swaps per pass (each taking time 1) done in parallel.
sorts in place. Returns the cost
"""
cost = 0
sortflag = False
addflag = False
while not sortflag:
sortflag = True
addflag = False
for i in range(1, len(L) - 1, 2):
if L[i] > L[i+1]:
L[i], L[i+1] = L[i+1], L[i]
sortflag = False
addflag = True
cost += int(addflag)
addflag = False
for i in range(0, len(L) - 1, 2):
if L[i] > L[i+1]:
L[i], L[i+1] = L[i+1], L[i]
sortflag = False
addflag = True
cost += int(addflag)
return cost
def apply_revs(revs, perm):
"""
Applies reversals in list revs in order to permutation perm, in place
......
from . import reversal, tripartite_binary_sort
from . import reversal
def GDC_TBS(perm):
"""
Returns the cost to sort perm using TBS as the binary sorting sequence.
(GenericDivideConquer_TripartiteBinarySort)
"""
revlist = routing_divideconquer_tbs(perm, 0, len(perm) - 1)
cost = reversal.compress_reversals(revlist, len(perm)) / 3
return cost
def median(L):
return len(L) // 2
def perm_to_01(L, i, j):
def perm_to_01(L):
"""
TUrns a permutation L[i:j] into a permutation of 0s and 1s, where L[i] is 0 if it is
less than the median and L[i] is 1 if it is greater than the median
"""
subseq = L[i:j + 1]
sorted_subseq = sorted(subseq)
median = (j - i) // 2
return L[:i] + [int(sorted_subseq.index(k) > median) for k in subseq] + L[j + 1:]
# Note that permutations are 0-indexed so we need to shift by one.
return [int(x + 1 > median(L)) for x in L]
class DCRoute:
"""Divide and Conquer routing algorithm"""
def __init__(self, sorting_alg):
"""Construct divide and conquer routing algorithm with a sorting algorithm"""
self.alg = sorting_alg
def routing_divideconquer_tbs(L, i=None, j=None):
def route(self, L):
"""
Sorts the given permutations L using a divide and conquer approach. Returns a list of
reversals used to perform the sort.
L must contain all elements in [0, len(L)-1] once.
"""
if len(L) <= 1:
return []
T = perm_to_01(L)
revs = self.alg(T)
for rev in revs:
rev.apply(L)
m = median(L)
left_revs = self.route(L[:m])
right_perm = [x - m for x in L[m:]]
right_revs = [rev.offset(m) for rev in self.route(right_perm)]
return revs + left_revs + right_revs
def odd_even_sort(L):
"""
Sorts the given permutations L from index i to j (inclusive) using a divide and conquer approach. Returns a list of
reversals used to perform the sort.
L: List to be sorted. Since we can reduce the problem of sorting according to a permutation
pi to just sorting according to the identity permutation, we just sort normally.
Odd even sort is performed in parallel. Each pass is an odd pass or even pass, where we look
at every odd/even pair and swap if they are out of order. Each pass takes time 1, since there are
n/2 swaps per pass (each taking time 1) done in parallel.
sorts in place. Returns the cost
"""
# Set default params
if i == None:
i = min(L)
if j == None:
j = max(L)
if i == j:
return []
T = perm_to_01(L, i, j)
revs = tripartite_binary_sort.tripartite_binary_sort(T, i, j)
m = (i + j) // 2
reversal.apply_revs(revs, L)
return revs + routing_divideconquer_tbs(L, i, m) + routing_divideconquer_tbs(L, m + 1, j)
cost = 0
sortflag = False
addflag = False
while not sortflag:
sortflag = True
addflag = False
for i in range(1, len(L) - 1, 2):
if L[i] > L[i+1]:
L[i], L[i+1] = L[i+1], L[i]
sortflag = False
addflag = True
cost += int(addflag)
addflag = False
for i in range(0, len(L) - 1, 2):
if L[i] > L[i+1]:
L[i], L[i+1] = L[i+1], L[i]
sortflag = False
addflag = True
cost += int(addflag)
return cost
......@@ -6,26 +6,33 @@ import math
from . import reversal
def tripartite_binary_sort(T, i, j):
def tripartite_binary_sort(T):
"""
Performs TBS (binary version) on T[i,j] inclusive, returns list of reversals
Performs TBS on T, returns list of reversals
"""
if all(T[ind] <= T[ind + 1] for ind in range(i, j)):
if all(T[ind] <= T[ind + 1] for ind in range(len(T) - 1)):
return []
part1, part2 = (2 * i + j) // 3, (i + 2 * j) // 3
part1, part2 = len(T) // 3, 2 * len(T) // 3
revlist = []
revlist += tripartite_binary_sort(T, i, part1)
flipbits(T, part1 + 1, part2)
revlist += tripartite_binary_sort(T, part1 + 1, part2)
flipbits(T, part1 + 1, part2)
revlist += tripartite_binary_sort(T, part2 + 1, j)
oneind, zerind = zero_one_indices(T, i, j)
revlist += tripartite_binary_sort(T[:part1 + 1])
middle_revs = tripartite_binary_sort(negatelist(T[part1 + 1: part2 + 1]))
revlist += [rev.offset(part1 + 1) for rev in middle_revs]
end_revs = tripartite_binary_sort(T[part2 + 1: len(T)])
revlist += [rev.offset(part2 + 1) for rev in end_revs]
# Leave T immutable
copyT = T.copy()
for rev in revlist:
rev.apply(copyT)
oneind, zerind = zero_one_indices(copyT, 0, len(T)-1)
if oneind < zerind:
rev = reversal.Reversal(oneind, zerind)
reversal.reverse(T, rev)
revlist.append(rev)
return revlist
def negatelist(T):
return [int(not x) for x in T]
def flipbits(T, i, j):
for index in range(i,j+1):
T[index] = int(not T[index])
......
import random
from reversal_sort import routing
from unittest import TestCase
from reversal_sort.routing import DCRoute
from reversal_sort.tripartite_binary_sort import tripartite_binary_sort
from reversal_sort.adaptive_tbs import adaptive_tb_sort
class TestRouting(TestCase):
def test_perm_sort(self):
def check_route_correctly(self, router, L):
L_orig = L.copy()
reversals = router.route(L.copy())
for rev in reversals:
rev.apply(L)
self.assertEqual(sorted(L_orig), L, msg=f"Did not route {L_orig}")
def test_route_tbs_simple(self):
L = [0, 1, 3, 2]
router = DCRoute(tripartite_binary_sort)
self.check_route_correctly(router, L)
def test_route_tbs_simple2(self):
L = [1, 3, 0, 4, 2]
router = DCRoute(tripartite_binary_sort)
self.check_route_correctly(router, L)
def test_route_tbs(self):
"""
Testing correctness of sorting permutations with TBS as
bitstring sorting subroutine.
Returns number
"""
n = 1000
router = DCRoute(tripartite_binary_sort)
for ct in range(n):
L = list(range(0,random.randint(5, 200)))
random.shuffle(L)
self.check_route_correctly(router, L)
def test_route_atbs(self):
"""
Testing correctness of sorting permutations with TBS as
bitstring sorting subroutine.
Returns number
"""
n = 1000
router = DCRoute(adaptive_tb_sort)
for ct in range(n):
L = list(range(1,random.randint(5, 200)))
L = list(range(0,random.randint(5, 50)))
random.shuffle(L)
before = L.copy()
routing.routing_divideconquer_tbs(L,0,len(L)-1)
self.assertEqual(sorted(before), L)
\ No newline at end of file
self.check_route_correctly(router, L)
\ No newline at end of file
import random
from reversal_sort import tripartite_binary_sort
from reversal_sort.tripartite_binary_sort import tripartite_binary_sort
from unittest import TestCase
class TestTBS(TestCase):
def test_simple(self):
l = [1, 0, 1, 0]
sortme = l.copy()
reversals = tripartite_binary_sort(sortme)
for reversal in reversals:
reversal.apply(sortme)
self.assertEqual(sortme, sorted(l))
def test_simple2(self):
l = [1, 0, 0, 1, 1]
sortme = l.copy()
reversals = tripartite_binary_sort(sortme)
for reversal in reversals:
reversal.apply(sortme)
self.assertEqual(sortme, sorted(l))
def test_binary_sort(self):
"""
Testing correctness of sorting bitstrings with TBS.
"""
for i in range(1000):
l = [0]*random.randint(100,200) + [1]*random.randint(100,200)
random.shuffle(l)
before = l.copy()
tripartite_binary_sort.tripartite_binary_sort(l, 0 , len(l)-1)
self.assertEqual(sorted(before), l)
\ No newline at end of file
shuffled = l.copy()
random.shuffle(shuffled)
shuffled_orig = shuffled.copy()
reversals = tripartite_binary_sort(shuffled)
for reversal in reversals:
reversal.apply(shuffled)
self.assertEqual(shuffled, l, msg=f"Did not sort {shuffled_orig}")
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment