def test_hill_climb_with_restrictions(): """ Same problem as the previous, except that all weights are 1, and edge [1,4] and [4,3] are not bidirectional. and the only restriction is that load 1 cannot travel over node 2. 1 1 [1]<--->[2]<--->[3] \ 1 1 / -->[4]--> """ g = Graph() for s, e in [(1, 2), (2, 3)]: g.add_edge(s, e, 1, bidirectional=True) for s, e in [(1, 4), (4, 3)]: g.add_edge(s, e, 1, bidirectional=False) loads = {1: (1, [3], [2]), # restriction 1 cannot travel over 2 2: [3, 1]} sequence = jam_solver(g, loads) expected_seq = [{2: (3, 2)}, {1: (1, 4)}, {1: (4, 3)}, {2: (2, 1)}] assert is_matching(sequence, expected_seq) concurrent_moves = jam_solver(g, loads, synchronous_moves=True) expected_conc_moves = [{1: (1, 4), 2: (3, 2)},{1: (4, 3), 2: (2, 1)}] assert is_matching(concurrent_moves, expected_conc_moves)
def test_simple_reroute_4(): """ 1 / \ 6---2 / \ / \ 5 - 4 - 3 """ g = Graph() edges = [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 1), (2, 6), (2, 4), (6, 2)] for s, e in edges: g.add_edge(s, e, 1, bidirectional=True) g.del_edge(4, 5) loads = {1: [1, 4], 3: [3, 1], 6: [6, 2]} sequence = jam_solver(g, loads, synchronous_moves=False) assert sequence == [{1: (1, 2)}, {1: (2, 4)}, {3: (3, 2)}, {3: (2, 1)}, {6: (6, 2)}] g.del_edge(2, 4) sequence = jam_solver(g, loads, synchronous_moves=False) expected = [{1: (1, 2)}, {3: (3, 4)}, {1: (2, 3)}, {3: (4, 2)}, {3: (2, 1)}, {6: (6, 2)}, {1: (3, 4)}] assert is_matching(sequence, expected)
def test_loop_9(): g = Graph( from_list=[(a, b, 1) for a, b in zip(range(1, 8), range(2, 9))] + [(8, 1, 1)] ) loads = {1: [1, 2], 2: [3, 4], 3: [5, 6], 4: [7, 8]} solution = jam_solver(g, loads, return_on_first=True) assert is_sequence_valid(solution, g) expected = [{4: (7, 8), 3: (5, 6), 2: (3, 4), 1: (1, 2)}] assert solution == expected sync_moves = jam_solver(g, loads, return_on_first=True, synchronous_moves=True) assert sync_moves == [{4: (7, 8), 3: (5, 6), 2: (3, 4), 1: (1, 2)}]
def test_energy_and_restrictions_3_loads(): """ See chart in example/images/tjs_problem_w_distance_restrictions.png NB: Lengths differ from image! """ g = Graph() for sed in [ (1, 2, 1), (3, 5, 2), # dead end. (1, 3, 7), # this is the most direct route for load 2, but it is 7 long. (3, 4, 1), (4, 6, 1), # this could be the shortest path for load 1, but # load 1 cannot travel over 4. If it could the path [2,3,4,6] would be 3 long. (2, 3, 1), (3, 6, 3), # this is the shortest unrestricted route for load 1: [2,3,6] and it is 4 long. (2, 6, 8), # this is the most direct route for load 1 [2,6] but it is 8 long. ]: g.add_edge(*sed, bidirectional=True) loads = {1: (2, [6], [4]), 2: (3, 1), 3: (5, 3)} moves = jam_solver(g, loads, synchronous_moves=False) assert is_sequence_valid(moves, g) assert len(moves) == 7 expected_moves = [ {2: (3, 4)}, # distance 1 {1: (2, 3)}, # distance 1 {1: (3, 6)}, # distance 3 {2: (4, 3)}, # distance 1 {2: (3, 2)}, # distance 1 {2: (2, 1)}, # distance 1 {3: (5, 3)} # distance 2 ] # total distance = (1+3)+(1+1+1+1)+(2) = 10 assert is_matching(moves, expected_moves), moves
def test_3_trains(): """ Two trains (abc & d) are going east. One train is going west (efgh). a-b-c--0-0-0--d--0--e-f-g-h \---0---/ \-0-/ 1-2-3--4-5-6--7--8---9-10-11-12 \---13--/ \-14-/ The solution is given by side stepping abc (on 4,5,6) & d (on 8) and letting efgh pass on (12, 11, 10, 9, 14, 7, 13, 3, 2, 1) """ g = Graph() edges = [ (3, 13), (13, 7), (7, 14), (14, 9) ] for a, b in zip(range(1, 12), range(2, 13)): edges.append((a, b)) for s, e in edges: g.add_edge(s, e, 1, bidirectional=True) loads = { 'a': [1, 10], 'b': [2, 11], 'c': [3, 12], 'd': [8, 9], # east bound 'e': [9, 1], 'f': [10, 2], 'g': [11, 3], 'h': [12, 4] # west bound } sequence = jam_solver(g, loads, return_on_first=True,timeout=40_000) assert sequence is not None
def test_energy_and_restrictions_3_loads_c(): """ See chart in example/images/tjs_problem_w_distance_restrictions.png NB: Lengths differ from image! """ g = Graph() for sed in [ (1, 2, 1), (3, 5, 1), # dead end. (1, 3, 7), # this is the most direct route for load 2, but it is 7 long. (3, 4, 1), (4, 6, 1), # this could be the shortest path for load 1, but # load 1 cannot travel over 4. If it could the path [2,3,4,6] would be 3 long. (2, 3, 1), (3, 6, 3), # this is the shortest unrestricted route for load 1: [2,3,6] and it is 4 long. (2, 6, 8), # this is the most direct route for load 1 [2,6] but it is 8 long. ]: g.add_edge(*sed, bidirectional=True) loads = {1: (2, [6], [4]), 2: (3, 1), 3: (4, 3)} # Load 3 blocks load two from moving in here. moves = jam_solver(g, loads, synchronous_moves=False) expected = [ {2: (3, 5)}, # load 2 moves out of the way at cost 1 {1: (2, 3)}, {1: (3, 6)}, # load 1 takes shortest permitted path. {2: (5, 3)}, {2: (3, 2)}, {2: (2, 1)}, # load 2 moves to destination. {3: (4, 3)} # load 3 moves to destination. ] # total distance 9 assert is_matching(moves, expected), moves
def test_loop_52(): g = Graph( from_list=[ (52, 1, 1), (1, 2, 1), (2, 3, 1), (3, 4, 1), (4, 5, 1), (5, 6, 1), (6, 7, 1), (7, 8, 1), (8, 9, 1), (9, 10, 1), (10, 11, 1), (11, 12, 1), (12, 13, 1), (13, 14, 1), (14, 15, 1), (15, 16, 1), (16, 17, 1), (17, 18, 1), (18, 19, 1), (19, 20, 1), (20, 21, 1), (21, 22, 1), (22, 23, 1), (23, 24, 1), (24, 25, 1), (25, 26, 1), (26, 27, 1), (27, 28, 1), (28, 29, 1), (29, 30, 1), (30, 31, 1), (31, 32, 1), (32, 33, 1), (33, 34, 1), (34, 35, 1), (35, 36, 1), (36, 37, 1), (37, 38, 1), (38, 39, 1), (39, 40, 1), (40, 41, 1), (41, 42, 1), (42, 43, 1), (43, 44, 1), (44, 45, 1), (45, 46, 1), (46, 47, 1), (47, 48, 1), (48, 49, 1), (49, 50, 1), (50, 51, 1), (51, 52, 1) ] ) loads = { 98: [52, 1], 55: [2, 3], 56: [3, 4], 57: [4, 5], 58: [5, 6], 59: [6, 7], 60: [7, 8], 61: [9, 10], 62: [10, 11], 63: [11, 12], 64: [12, 13], 65: [14, 15], 66: [15, 16], 67: [16, 17], 68: [17, 18], 69: [18, 19], 70: [19, 20], 71: [21, 22], 72: [22, 23], 73: [23, 24], 74: [24, 25], 75: [25, 26], 76: [26, 27], 77: [28, 29], 78: [29, 30], 79: [30, 31], 80: [31, 32], 81: [32, 33], 82: [33, 34], 83: [35, 36], 84: [36, 37], 85: [37, 38], 86: [38, 39], 87: [39, 40], 88: [40, 41], 89: [42, 43], 90: [43, 44], 91: [44, 45], 92: [45, 46], 93: [46, 47], 94: [47, 48], 95: [49, 50], 96: [50, 51], 97: [51, 52] } solution = jam_solver(g, loads, return_on_first=True, timeout=1_000) assert is_sequence_valid(solution, g) loads2 = check_user_input(g, loads) concurrent_moves = moves_to_synchronous_moves(solution, loads2) assert concurrent_moves == [ {98: (52, 1), 60: (7, 8), 59: (6, 7), 58: (5, 6), 57: (4, 5), 56: (3, 4), 55: (2, 3), 64: (12, 13), 63: (11, 12), 62: (10, 11), 61: (9, 10), 70: (19, 20), 69: (18, 19), 68: (17, 18), 67: (16, 17), 66: (15, 16), 65: (14, 15), 76: (26, 27), 75: (25, 26), 74: (24, 25), 73: (23, 24), 72: (22, 23), 71: (21, 22), 82: (33, 34), 81: (32, 33), 80: (31, 32), 79: (30, 31), 78: (29, 30), 77: (28, 29), 88: (40, 41), 87: (39, 40), 86: (38, 39), 85: (37, 38), 84: (36, 37), 83: (35, 36), 94: (47, 48), 93: (46, 47), 92: (45, 46), 91: (44, 45), 90: (43, 44), 89: (42, 43), 97: (51, 52), 96: (50, 51), 95: (49, 50)} ], "something is wrong. All moves CAN happen at the same time."
def test_energy_and_restrictions_2_loads(): """ See chart in example/images/tjs_problem_w_distance_restrictions.png NB: LENGTHS DIFFER FROM IMAGE! """ g = Graph() for sed in [ (1, 2, 1), (3, 5, 2), # dead end. (1, 3, 7), # this is the most direct route for load 2, but it is 7 long. (3, 4, 1), (4, 6, 1), # this could be the shortest path for load 1, but # load 1 cannot travel over 4. If it could the path [2,3,4,6] would be 3 long. (2, 3, 1), (3, 6, 3), # this is the shortest unrestricted route for load 1: [2,3,6] and it is 4 long. (2, 6, 8), # this is the most direct route for load 1 [2,6] but it is 8 long. ]: g.add_edge(*sed, bidirectional=True) loads = {1: (2, [6], [4]), # restriction load 1 cannot travel over 4 2: (3, 1)} moves = jam_solver(g, loads, synchronous_moves=False) expected= [ {2: (3, 4)}, # distance = 1, Load2 moves out of Load1's way. {1: (2, 3)}, # distance = 1 {1: (3, 6)}, # distance = 3 {2: (4, 3)}, # distance = 1, Load2 moves back onto it's starting point. {2: (3, 2)}, # distance = 1 {2: (2, 1)} # distance = 1 ] # total distance = (1+3)+(1+1+1+1) = 8 assert is_matching(moves, expected)
def test_clockwise_rotation(): """ A simple loop of 4 locations, where 3 loads need to move clockwise. """ g = Graph() edges = [ (1, 2), (2, 3), (3, 4), (4, 1), ] for s, e in edges: g.add_edge(s, e, 1, bidirectional=True) loads = {1: [1, 2], 2: [2, 3], 3: [3, 4]} # position 4 is empty. sequence = jam_solver(g, loads) assert sequence == [ { 3: (3, 4) }, # first move. { 2: (2, 3) }, # second move. { 1: (1, 2) } ], sequence # last move.
def test_energy_and_restrictions_2_load_high_detour_costs(): """ See chart in example/images/tjs_problem_w_distance_restrictions.png """ g = Graph() for sed in [ (1, 2, 1), (1, 3, 70), # this is the most direct route for load 2, but it is 70 long. (3, 5, 2), # 5 is a dead end at higher cost than going (3,4) (3, 4, 1), (4, 6, 1), # this could be shortest path for load 1, but # load 1 cannot travel over 4. If it could the path [2,3,4,6] would be 3 long. (2, 3, 1), (3, 6, 3), # this is the shortest unrestricted route for load 1: [2,3,6] and it is 4 long. (2, 6, 50), # this is the most direct route for load 1 [2,6] but it is 50 long. ]: g.add_edge(*sed, bidirectional=True) loads = {"A": (2, [6], [4]), "B": (3, 1)} moves = jam_solver(g, loads, synchronous_moves=False, return_on_first=False) assert is_sequence_valid(moves, g) assert len(moves) == 6 expected = [ {"B": (3, 4)}, # 2 moves into the dead end. {"A": (2, 3)}, # 1 moves into where 2 was. {"A": (3, 6)}, # 1 moves onto destination. {"B": (4, 3)}, # 2 moves back to origin along path [4,3,2,1] {"B": (3, 2)}, {"B": (2, 1)} ] assert is_matching(moves, expected), moves
def test_loop_9(): g = Graph(from_list=[(a, b, 1) for a, b in zip(range(1, 8), range(2, 9))] + [(8, 1, 1)]) loads = {1: [1, 2], 2: [3, 4], 3: [5, 6], 4: [7, 8]} solution = jam_solver(g, loads) expected = [{4: (7, 8)}, {3: (5, 6)}, {2: (3, 4)}, {1: (1, 2)}] for v in solution: assert v in expected, v
def test_2_trains(): """ two trains of loads are approaching each other. train 123 going from 1 to 14 train 4567 going from 14 to 1. At intersection 4 train 123 can be broken apart and buffered, so that train 4567 can pass. The reverse (buffering train 4567) is not possible. [1]--[2]--[3]--[4]--[5]--[9]--[10]--[11]--[12]--[13]--[14] +---[6]---+ +---[7]---+ +---[8]---+ """ g = Graph() edges = [ (1, 2), (2, 3), (3, 4), (4, 5), (4, 6), (4, 7), (4, 8), (5, 9), (6, 9), (7, 9), (8, 9), (9, 10), (10, 11), (11, 12), (12, 13), (13, 14), ] for s, e in edges: g.add_edge(s, e, 1, bidirectional=True) loads = { 41: [1, 12], 42: [2, 13], 43: [3, 14], 44: [11, 1], 45: [12, 2], 46: [13, 3], 47: [14, 4], } sequence = jam_solver(g, loads, return_on_first=True, timeout=180_000) assert is_sequence_valid(sequence, g) expected = [{43: (3, 4)}, {43: (4, 6)}, {42: (2, 3)}, {42: (3, 4)}, {42: (4, 7)}, {41: (1, 2)}, {41: (2, 3)}, {41: (3, 4)}, {41: (4, 5)}, {44: (11, 10)}, {44: (10, 9)}, {44: (9, 8)}, {44: (8, 4)}, {44: (4, 3)}, {44: (3, 2)}, {44: (2, 1)}, {45: (12, 11)}, {45: (11, 10)}, {45: (10, 9)}, {45: (9, 8)}, {45: (8, 4)}, {45: (4, 3)}, {45: (3, 2)}, {46: (13, 12)}, {46: (12, 11)}, {46: (11, 10)}, {46: (10, 9)}, {46: (9, 8)}, {46: (8, 4)}, {46: (4, 3)}, {47: (14, 13)}, {47: (13, 12)}, {47: (12, 11)}, {47: (11, 10)}, {47: (10, 9)}, {47: (9, 8)}, {47: (8, 4)}, {43: (6, 9)}, {43: (9, 10)}, {43: (10, 11)}, {43: (11, 12)}, {43: (12, 13)}, {43: (13, 14)}, {42: (7, 9)}, {42: (9, 10)}, {42: (10, 11)}, {42: (11, 12)}, {42: (12, 13)}, {41: (5, 9)}, {41: (9, 10)}, {41: (10, 11)}, {41: (11, 12)}] assert is_matching(expected, sequence), sequence
def test_small_gridlock(): """ a grid lock is given, solver solves it.""" g = Graph() edges = [ (1, 2), (1, 4), (2, 3), (2, 5), (3, 6), (4, 5), (5, 6), (4, 7), (5, 8), (6, 9), (7, 8), (8, 9) ] for s, e in edges: g.add_edge(s, e, 1, bidirectional=True) loads = {'a': [2, 1], 'b': [5, 2], 'c': [4, 3], 'd': [8], 'e': [1, 9]} results = [] # TRIAL - 1 start = process_time() moves = jam_solver(g, loads) e = process_time() - start concurrent = jam_solver(g, loads, synchronous_moves=True) d = sum(len(d) for d in moves) results.append((e, d, len(concurrent))) assert d == 11, d expected = [{'b': (5, 6), 'c': (4, 5), 'e': (1, 4)}, {'b': (6, 3), 'c': (5, 6), 'e': (4, 5), 'a': (2, 1)}, {'b': (3, 2), 'c': (6, 3), 'e': (5, 6)}, {'e': (6, 9)}] assert all(m in expected for m in moves), moves assert len(concurrent) == 4 # TRIAL - 2 start = process_time() moves = jam_solver(g, loads) e = process_time() - start concurrent = jam_solver(g, loads, synchronous_moves=True) d = sum(len(d) for d in moves) results.append((e, d, len(concurrent))) for e, d, c in results: print("duration:", round(e, 4), "| distance", d, "| concurrent moves", c) results.clear()
def test_simple_reroute(): """ to loads on a collision path. """ g = Graph() for s, e in [(1, 2), (2, 3)]: g.add_edge(s, e, 1, bidirectional=True) for s, e in [(1, 4), (4, 3)]: g.add_edge(s, e, 1, bidirectional=False) loads = {1: [1, 3], 2: [3, 1]} concurrent_moves = jam_solver(g, loads, synchronous_moves=True) expected = [{1: (1, 4), 2: (3, 2)}, {1: (4, 3), 2: (2, 1)}] assert is_matching(concurrent_moves, expected), concurrent_moves
def test_hill_climb_with_edge_different_weights(): """ Same test as the previous, but this time edge 1<-->3 has weight 3, whilst the rest have weight 1. 3 1 [1]<--->[2]<--->[3] \ 1 1 / <->[4]<-> """ g = Graph() for sed in [(1, 2, 3), # <-- 3! (2, 3, 1), (1, 4, 1), (4, 3, 1)]: g.add_edge(*sed, bidirectional=True) loads = {1: [1, 3], 2: [3, 1]} moves = jam_solver(g, loads) expected_moves = [{2: (3, 4)}, {1: (1, 2)}, {2: (4, 1)}, {1: (2, 3)}] # reverse of previous test. assert is_matching(moves, expected_moves) concurrent_moves = jam_solver(g, loads, synchronous_moves=True) expected = [{2: (3, 4), 1: (1, 2)}, {2: (4, 1), 1: (2, 3)}] assert is_matching(concurrent_moves, expected)
def test_simple_failed_path(): """ two colliding loads with no solution """ g = Graph() for s, e in [(1, 2), (2, 3)]: g.add_edge(s, e, 1, bidirectional=True) loads = {1: [1, 3], 2: [3, 1]} try: _ = jam_solver(g, loads, return_on_first=True, timeout=200) assert False, "The problem is unsolvable." except NoSolution: assert True
def test_incomplete_graph(): """ two loads with an incomplete graph making the problem unsolvable """ g = Graph() for s, e in [(1, 2), (2, 3)]: g.add_edge(s, e, 1, bidirectional=True) g.add_node(5) loads = {1: [1, 5], 2: [5, 1]} try: _ = jam_solver(g, loads, timeout=200) assert False, "There is no path." except UnSolvable as e: assert str(e) == 'load 1 has no path from 1 to 5'
def test_hill_climb_with_edge_weights(): """ 1 1 [1]<--->[2]<--->[3] \ 3 1 / <->[4]<-> All edges are weight 1, except 1<->4 which has weight 3. """ g = Graph() for sed in [(1, 2, 1), (2, 3, 1), (1, 4, 3), # <-- 3! (4, 3, 1)]: g.add_edge(*sed, bidirectional=True) loads={1: [1, 3], 2: [3, 1]} moves = jam_solver(g,loads) is_sequence_valid(moves, g) expected = [{2: (3, 4)}, {1: (1, 2)}, {2: (4, 1)}, {1: (2, 3)}] assert is_matching(moves, expected) concurrent_moves = jam_solver(g, loads, synchronous_moves=True) expected_conc_moves = [{2: (3, 4), 1: (1, 2)}, {2: (4, 1), 1: (2, 3)}] assert is_matching(concurrent_moves, expected_conc_moves)
def test_snake_gridlock(): """ A bad route was given to train abcd, and now the train has gridlocked itself. 9 - 10 - 11 - 12 | 1 - 2 - 3 - 4 - 5d-> 6c ^ | | v 8a - 7b :return: """ g = Graph() edges = [(a, b) for a, b in zip(range(1, 12), range(2, 13)) if (a, b) != (8, 9)] for s, e in edges: g.add_edge(s, e, 1, bidirectional=True) g.add_edge(8, 5, 1, bidirectional=True) g.add_edge(5, 9, 1, bidirectional=True) loads = {'a': [8, 12], 'b': [7, 11], 'c': [6, 10], 'd': [5, 9]} sequence = jam_solver(g, loads, synchronous_moves=False, return_on_first=False) sync_moves = moves_to_synchronous_moves(sequence, check_user_input(g, loads)) expected = [{'d': (5, 4)}, # d goes one step back. {'a': (8, 5)}, # a moves forward towards its destination. {'b': (7, 8)}, # b moves forward to it's destination. {'a': (5, 9)}, {'b': (8, 5)}, {'a': (9, 10)}, {'b': (5, 9)}, {'c': (6, 5)}, # c moves forward. {'a': (10, 11)}, {'b': (9, 10)}, {'c': (5, 9)}, {'d': (4, 5)}, {'a': (11, 12)}, {'b': (10, 11)}, {'c': (9, 10)}, {'d': (5, 9)}] # d does a left turn (shortcut). assert is_matching(sequence, expected) expected = [{'d': (5, 4), 'a': (8, 5), 'b': (7, 8)}, {'a': (5, 9), 'b': (8, 5)}, {'a': (9, 10), 'b': (5, 9), 'c': (6, 5)}, {'a': (10, 11), 'b': (9, 10), 'c': (5, 9), 'd': (4, 5)}, {'a': (11, 12), 'b': (10, 11), 'c': (9, 10), 'd': (5, 9)}] assert is_matching(expected, sync_moves), sync_moves
def test_compact_bfs_problem(): """ [4]-->----+ | | [1]--[2]--[3] v | | [5]--<----+ find the shortest path for the collision between load on [1] and load on [2] """ g = Graph(from_list=[(1, 2, 1), (2, 3, 1), (4, 2, 1), (2, 5, 1), (4, 5, 3)]) # edge 4,5 has distance 3, which is longer than path [4,2,5] which has distance 2. moves = jam_solver(g, loads={1: [1, 3], 2: [4, 5]}) assert is_matching(moves, [{1: (1, 2)}, {1: (2, 3)}, {2: (4, 2)}, {2: (2, 5)}])
def test_hill_climb(): """ [1]<--->[2]<--->[3] \ / +->[4]->+ single direction! """ g = Graph() for s, e in [(1, 2), (2, 3)]: g.add_edge(s, e, 1, bidirectional=True) for s, e in [(1, 4), (4, 3)]: g.add_edge(s, e, 1, bidirectional=False) loads = {1: [1, 3], 2: [3, 1]} moves = jam_solver(g,loads, synchronous_moves=True) expected = [{1: (1, 4), 2: (3, 2)}, {2: (2, 1), 1: (4, 3)}] assert is_matching(moves, expected), moves
def test_simple_reroute_3(): """ Loop with 6 nodes: 1 <--> 2 <--> 3 <--> 4 <-- 5 <--> 6 <--> (1) """ g = Graph() edges = [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 1)] for s, e in edges: g.add_edge(s, e, 1, bidirectional=True) g.del_edge(4, 5) loads = {1: [1, 3], 2: [3, 1]} sequence = jam_solver(g, loads, synchronous_moves=False) expected = [{1: (1, 6)}, {1: (6, 5)}, {1: (5, 4)}, {2: (3, 2)}, {1: (4, 3)}, {2: (2, 1)}] assert is_matching(sequence, expected) assert is_sequence_valid(sequence, g)
def test_simple_reroute(): """ to loads on a collision path. """ g = Graph() for s, e in [(1, 2), (2, 3)]: g.add_edge(s, e, 1, bidirectional=True) for s, e in [(1, 4), (4, 3)]: g.add_edge(s, e, 1, bidirectional=False) loads = {1: [1, 2, 3], 2: [3, 2, 1]} sequence = jam_solver(g, loads) solution1 = [{1: (1, 4)}, {2: (3, 2)}, {2: (2, 1)}, {1: (4, 3)}] solution2 = [{1: (1, 4)}, {2: (3, 2)}, {1: (4, 3)}, {2: (2, 1)}] assert sequence in [solution1, solution2] concurrent_moves = moves_to_synchronous_moves(sequence, loads) assert concurrent_moves == [{1: (1, 4), 2: (3, 2)}, {2: (2, 1), 1: (4, 3)}]
def test_simple_reroute_2(): """ to loads on a collision path. [1]<-->[2]<-->[3]<-->[4] | ^ +---->[5]-->[6]------+ """ g = Graph() for s, e in [(1, 2), (2, 3), (3, 4)]: g.add_edge(s, e, 1, bidirectional=True) for s, e in [(1, 5), (5, 6), (6, 4)]: g.add_edge(s, e, 1, bidirectional=False) loads = {1: [1, 4], 2: [4, 1]} sequence = jam_solver(g, loads, synchronous_moves=False) atomic_sequence = [{1: (1, 5)}, {1: (5, 6)}, {2: (4, 3)}, {1: (6, 4)}, {2: (3, 2)}, {2: (2, 1)}] assert is_matching(sequence, atomic_sequence) assert is_sequence_valid(sequence, g)
def test_simple_reroute_2(): """ to loads on a collision path. """ g = Graph() for s, e in [(1, 2), (2, 3), (3, 4)]: g.add_edge(s, e, 1, bidirectional=True) for s, e in [(1, 5), (5, 6), (6, 4)]: g.add_edge(s, e, 1, bidirectional=False) loads = {1: [1, 2, 3, 4], 2: [4, 3, 2, 1]} sequence = jam_solver(g, loads) s1 = [{ 1: (1, 5) }, { 2: (4, 3) }, { 2: (3, 2) }, { 2: (2, 1) }, { 1: (5, 6) }, { 1: (6, 4) }] s2 = [{ 1: (1, 5) }, { 1: (5, 6) }, { 2: (4, 3) }, { 1: (6, 4) }, { 2: (3, 2) }, { 2: (2, 1) }] solutions = [s1, s2] assert sequence in solutions
def test_shuffle2(): g = Graph() edges = [(1, 2), (2, 3), (3, 5), (3, 6), (5, 6), (6, 7), (7, 8)] for s, e in edges: g.add_edge(s, e, 1, bidirectional=True) loads = { 1: (1, 7), 2: (2, g.nodes()), 3: (8, g.nodes()) } sequence = jam_solver(g, loads, synchronous_moves=False, return_on_first=True) expected = [ {2: (2, 3)}, {2: (3, 5)}, {1: (1, 2)}, {1: (2, 3)}, {1: (3, 6)}, {1: (6, 7)}] assert is_matching(sequence, expected), sequence
def test_simple_reroute_3(): """ Loop with 6 nodes: 1 <--> 2 <--> 3 <--> 4 <-- 5 <--> 6 <--> (1) """ g = Graph() edges = [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 1)] for s, e in edges: g.add_edge(s, e, 1, bidirectional=True) g.del_edge(4, 5) loads = {1: [1, 2, 3], 2: [3, 2, 1]} sequence = jam_solver(g, loads) assert sequence == [{ 1: (1, 6) }, { 2: (3, 2) }, { 2: (2, 1) }, { 1: (6, 5) }, { 1: (5, 4) }, { 1: (4, 3) }], sequence a = bfs_resolve(g, loads) b = bi_directional_bfs(g, loads) c = bi_directional_progressive_bfs(g, loads) for d in a: b.remove(d) c.remove(d) # all moves known in a have been removed from b and c. assert b == c == []
def test_5x5_graph(): g = graph5x5() loads = { 'a': [6], 'b': [11, 1], 'c': [16, 2], 'd': [17, 4], 'e': [19, 5], 'f': [20, 3] } sequence = jam_solver(g, loads) s1 = [{ 'a': (6, 7) }, { 'b': (11, 6) }, { 'b': (6, 1) }, { 'a': (7, 6) }, { 'c': (16, 11) }, { 'd': (17, 18) }, { 'd': (18, 13) }, { 'd': (13, 14) }, { 'd': (14, 9) }, { 'd': (9, 4) }, { 'f': (20, 15) }, { 'f': (15, 14) }, { 'f': (14, 13) }, { 'f': (13, 8) }, { 'f': (8, 3) }, { 'e': (19, 20) }, { 'e': (20, 15) }, { 'e': (15, 10) }, { 'e': (10, 5) }, { 'c': (11, 12) }, { 'c': (12, 7) }, { 'c': (7, 2) }] s2 = [{ 'b': (11, 12) }, { 'b': (12, 7) }, { 'b': (7, 2) }, { 'b': (2, 1) }, { 'c': (16, 11) }, { 'c': (11, 12) }, { 'c': (12, 7) }, { 'c': (7, 2) }, { 'e': (19, 14) }, { 'e': (14, 9) }, { 'e': (9, 4) }, { 'e': (4, 5) }, { 'd': (17, 12) }, { 'd': (12, 7) }, { 'd': (7, 8) }, { 'd': (8, 3) }, { 'd': (3, 4) }, { 'f': (20, 15) }, { 'f': (15, 10) }, { 'f': (10, 9) }, { 'f': (9, 8) }, { 'f': (8, 3) }] solutions = [s1, s2] assert sequence in solutions
def test_2_trains(): """ two trains of loads are approaching each other. train 123 going from 1 to 14 train 4567 going from 14 to 1. At intersection 4 train 123 can be broken apart and buffered, so that train 4567 can pass. The reverse (buffering train 4567) is not possible. """ g = Graph() edges = [ (1, 2), (2, 3), (3, 4), (4, 5), (4, 6), (4, 7), (4, 8), (5, 9), (6, 9), (7, 9), (8, 9), (9, 10), (10, 11), (11, 12), (12, 13), (13, 14), ] for s, e in edges: g.add_edge(s, e, 1, bidirectional=True) loads = { 41: [1, 2, 3, 4, 5, 9, 10, 11, 12], 42: [2, 3, 4, 5, 9, 10, 11, 12, 13], 43: [3, 4, 5, 9, 10, 11, 12, 13, 14], 44: [11, 10, 9, 5, 4, 3, 2, 1], 45: [12, 11, 10, 9, 5, 4, 3, 2], 46: [13, 12, 11, 10, 9, 5, 4, 3], 47: [14, 13, 12, 11, 10, 9, 5, 4], } sequence = jam_solver(g, loads) assert sequence == [{ 43: (3, 4) }, { 43: (4, 6) }, { 42: (2, 3) }, { 42: (3, 4) }, { 42: (4, 7) }, { 41: (1, 2) }, { 41: (2, 3) }, { 41: (3, 4) }, { 41: (4, 5) }, { 44: (11, 10) }, { 44: (10, 9) }, { 44: (9, 8) }, { 44: (8, 4) }, { 44: (4, 3) }, { 44: (3, 2) }, { 44: (2, 1) }, { 45: (12, 11) }, { 45: (11, 10) }, { 45: (10, 9) }, { 45: (9, 8) }, { 45: (8, 4) }, { 45: (4, 3) }, { 45: (3, 2) }, { 46: (13, 12) }, { 46: (12, 11) }, { 46: (11, 10) }, { 46: (10, 9) }, { 46: (9, 8) }, { 46: (8, 4) }, { 46: (4, 3) }, { 47: (14, 13) }, { 47: (13, 12) }, { 47: (12, 11) }, { 47: (11, 10) }, { 47: (10, 9) }, { 47: (9, 8) }, { 47: (8, 4) }, { 43: (6, 9) }, { 43: (9, 10) }, { 43: (10, 11) }, { 43: (11, 12) }, { 43: (12, 13) }, { 43: (13, 14) }, { 42: (7, 9) }, { 42: (9, 10) }, { 42: (10, 11) }, { 42: (11, 12) }, { 42: (12, 13) }, { 41: (5, 9) }, { 41: (9, 10) }, { 41: (10, 11) }, { 41: (11, 12) }], sequence
def test_5x5_graph(): g = graph5x5() loads = {'a': [6], 'b': [11, 1], 'c': [16, 2], 'd': [17, 4], 'e': [19, 5], 'f': [20, 3]} sequence = jam_solver(g, loads, return_on_first=True, timeout=30_000) assert is_sequence_valid(sequence, g)