|  | from collections import namedtuple | 
|  | import pprint | 
|  | from enum import Enum | 
|  |  | 
|  |  | 
|  | class Direction(Enum): | 
|  | NO_SIDE = 0 | 
|  | LEFT = 1 | 
|  | RIGHT = 2 | 
|  | TOP = 3 | 
|  | BOTTOM = 4 | 
|  | RIGHT_LEFT = 5 | 
|  | RIGHT_BOTTOM = 6 | 
|  | RIGHT_BOTTOM_LEFT = 7 | 
|  | TOP_RIGHT = 8 | 
|  | TOP_BOTTOM = 9 | 
|  | TOP_LEFT = 10 | 
|  | TOP_RIGHT_BOTTOM = 11 | 
|  | TOP_RIGHT_LEFT = 12 | 
|  | TOP_BOTTOM_LEFT = 13 | 
|  | TOP_RIGHT_BOTTOM_LEFT = 14 | 
|  | BOTTOM_LEFT = 15 | 
|  |  | 
|  |  | 
|  | # Track (aka Channel) is a wire that runs from one position to another along X or Y axis | 
|  | Track = namedtuple('Track', 'direction x_low x_high y_low y_high') | 
|  |  | 
|  |  | 
|  | def print_tracks(tracks): | 
|  | pprint.pprint(tracks) | 
|  |  | 
|  |  | 
|  | def make_tracks(xs, ys, points, grid_width=None, grid_height=None): | 
|  | """ Give a list of xs columns and ys rows and points, return a list of | 
|  | Track's and connections between the tracks. | 
|  |  | 
|  | An assert will fail if each point in the point list is not covered by | 
|  | a column in xs or a row in ys. | 
|  |  | 
|  | Connections will be models as indicies into the track list. | 
|  |  | 
|  | Return: | 
|  | [Track], [(index into track list, index into track list)] | 
|  |  | 
|  | >>> pos = [ | 
|  | ... (1,1),        (3,1), | 
|  | ... (1,2), (2,2), (3,2), | 
|  | ... (1,3),        (3,3), | 
|  | ... (1,4), (2,4), (3,4), | 
|  | ... (1,5),        (3,5), | 
|  | ... ] | 
|  | >>> xs = [1, 3] | 
|  | >>> ys = [2, 4] | 
|  | >>> tracks, connections = make_tracks(xs, ys, pos) | 
|  | >>> print_tracks(tracks) | 
|  | [Track(direction='Y', x_low=1, x_high=1, y_low=1, y_high=5), | 
|  | Track(direction='Y', x_low=3, x_high=3, y_low=1, y_high=5), | 
|  | Track(direction='X', x_low=1, x_high=3, y_low=2, y_high=2), | 
|  | Track(direction='X', x_low=1, x_high=3, y_low=4, y_high=4)] | 
|  | >>> print(sorted(connections)) | 
|  | [(2, 0), (2, 1), (3, 0)] | 
|  |  | 
|  | >>> pos = [ | 
|  | ... (68,48), (69,48), | 
|  | ... (68,49), (69,49), | 
|  | ...          (69,50), | 
|  | ...          (69,51), | 
|  | ...          (69,52), | 
|  | ...          (69,53), (70,53), (71,53), (72,53)] | 
|  | >>> xs = [68, 69] | 
|  | >>> ys = [53] | 
|  | >>> tracks, connections = make_tracks(xs, ys, pos) | 
|  | >>> print_tracks(tracks) | 
|  | [Track(direction='Y', x_low=68, x_high=68, y_low=48, y_high=53), | 
|  | Track(direction='Y', x_low=69, x_high=69, y_low=48, y_high=53), | 
|  | Track(direction='X', x_low=68, x_high=72, y_low=53, y_high=53)] | 
|  | >>> print(connections) | 
|  | [(2, 0), (2, 1)] | 
|  |  | 
|  |  | 
|  | """ | 
|  | x_set = set(xs) | 
|  | y_set = set(ys) | 
|  |  | 
|  | for x, y in points: | 
|  | if x > 0: | 
|  | in_x_set = x in x_set or x - 1 in x_set, (x, x_set) | 
|  | else: | 
|  | in_x_set = x in x_set | 
|  |  | 
|  | if y > 0: | 
|  | in_y_set = y in y_set or y - 1 in y_set, (y, y_set) | 
|  | else: | 
|  | in_y_set = y in y_set | 
|  |  | 
|  | assert in_x_set or in_y_set | 
|  |  | 
|  | all_xs, all_ys = zip(*points) | 
|  | x_min = min(all_xs) | 
|  | x_max = max(all_xs) | 
|  | y_min = min(all_ys) | 
|  | y_max = max(all_ys) | 
|  |  | 
|  | tracks = [] | 
|  | x_tracks = [] | 
|  | y_tracks = [] | 
|  | for x in xs: | 
|  | y_low = max(y_min, 1) | 
|  | y_high = y_max | 
|  | if grid_height is not None: | 
|  | y_high = min(y_max, grid_height - 2) | 
|  |  | 
|  | tracks.append( | 
|  | Track( | 
|  | direction='Y', | 
|  | x_low=x, | 
|  | x_high=x, | 
|  | y_low=y_low, | 
|  | y_high=y_high, | 
|  | ) | 
|  | ) | 
|  | y_tracks.append(len(tracks) - 1) | 
|  |  | 
|  | for y in ys: | 
|  | x_low = max(x_min, 1) | 
|  | x_high = x_max | 
|  | if grid_width is not None: | 
|  | x_high = min(x_high, grid_width - 2) | 
|  |  | 
|  | tracks.append( | 
|  | Track( | 
|  | direction='X', | 
|  | x_low=x_low, | 
|  | x_high=x_high, | 
|  | y_low=y, | 
|  | y_high=y, | 
|  | ) | 
|  | ) | 
|  | x_tracks.append(len(tracks) - 1) | 
|  |  | 
|  | if len(tracks) == 1: | 
|  | return tracks, [] | 
|  |  | 
|  | # If there is more than 1 track, there must be a track in each dimension | 
|  | assert len(xs) >= 1 and len(ys) >= 1 | 
|  |  | 
|  | connections = set() | 
|  |  | 
|  | # Always just connect X tracks to the first Y track, and Y tracks to the | 
|  | # first X tracks. | 
|  | # | 
|  | # To dedup connections, the x channel track will appear first in the | 
|  | # connection list. | 
|  | for idx, track in enumerate(tracks): | 
|  | if track.direction == 'X': | 
|  | connections.add((idx, y_tracks[0])) | 
|  | else: | 
|  | assert track.direction == 'Y' | 
|  | connections.add((x_tracks[0], idx)) | 
|  |  | 
|  | return tracks, list(connections) | 
|  |  | 
|  |  | 
|  | class Tracks(object): | 
|  | """Tracks groups tracks and their connections (AKA Node). | 
|  | """ | 
|  |  | 
|  | def __init__(self, tracks, track_connections): | 
|  | self.tracks = tracks | 
|  | self.track_connections = track_connections | 
|  |  | 
|  | self.track_cache = {} | 
|  |  | 
|  | def verify_tracks(self): | 
|  | """ Verify that all tracks are connected to all other tracks. """ | 
|  | track_connections = {} | 
|  |  | 
|  | for idx, _ in enumerate(self.tracks): | 
|  | track_connections[idx] = set((idx, )) | 
|  |  | 
|  | for conn_a, conn_b in self.track_connections: | 
|  | if track_connections[conn_a] is track_connections[conn_b]: | 
|  | continue | 
|  |  | 
|  | assert self.tracks[conn_a].direction != self.tracks[conn_b | 
|  | ].direction | 
|  |  | 
|  | track_connections[conn_a] |= track_connections[conn_b] | 
|  |  | 
|  | for track_idx in track_connections[conn_a]: | 
|  | track_connections[track_idx] = track_connections[conn_a] | 
|  |  | 
|  | assert len( | 
|  | set(id(s) for s in track_connections.values()) | 
|  | ) == 1, track_connections | 
|  |  | 
|  | def is_wire_adjacent_to_track(self, idx, coord): | 
|  | """returns direction if wire at coord is adjacent to track at index idx | 
|  | returns NO_SIDE if not adjacent. | 
|  | """ | 
|  | track = self.tracks[idx] | 
|  | wire_x, wire_y = coord | 
|  |  | 
|  | if track.direction == 'X': | 
|  | pin_top = track.y_low == wire_y | 
|  | pin_bottom = track.y_low == wire_y - 1 | 
|  | adjacent_channel = (pin_top or pin_bottom) and ( | 
|  | track.x_low <= wire_x and wire_x <= track.x_high | 
|  | ) | 
|  |  | 
|  | if adjacent_channel: | 
|  | if pin_top: | 
|  | return Direction.TOP | 
|  | elif pin_bottom: | 
|  | return Direction.BOTTOM | 
|  | else: | 
|  | assert False, (coord, track) | 
|  | else: | 
|  | return Direction.NO_SIDE | 
|  |  | 
|  | elif track.direction == 'Y': | 
|  | pin_right = track.x_low == wire_x | 
|  | pin_left = track.x_low == wire_x - 1 | 
|  | adjacent_channel = (pin_right or pin_left) and ( | 
|  | track.y_low <= wire_y and wire_y <= track.y_high | 
|  | ) | 
|  |  | 
|  | if adjacent_channel: | 
|  | if pin_right: | 
|  | return Direction.RIGHT | 
|  | elif pin_left: | 
|  | return Direction.LEFT | 
|  | else: | 
|  | assert False, (coord, track) | 
|  | else: | 
|  | return Direction.NO_SIDE | 
|  | else: | 
|  | assert False, track | 
|  |  | 
|  | def get_tracks_for_wire_at_coord(self, coord): | 
|  | """Returns map of direction to track indicies for valid connections. | 
|  |  | 
|  | There may be multiple connections to tracks at a particular coordinate, | 
|  | this method only returns a possible connection in each direction to | 
|  | this track. | 
|  |  | 
|  | Parameters | 
|  | ---------- | 
|  | coord : (int, int) | 
|  | Coordinate to attach to track | 
|  |  | 
|  | Returns | 
|  | ------- | 
|  | dict(Direction -> int) | 
|  | Dictionary of pin direction to track index. | 
|  |  | 
|  | """ | 
|  |  | 
|  | if coord in self.track_cache: | 
|  | return self.track_cache[coord] | 
|  | else: | 
|  | wire_x, wire_y = coord | 
|  |  | 
|  | conns = {} | 
|  | for idx, track in enumerate(self.tracks): | 
|  | pin_dir = self.is_wire_adjacent_to_track(idx, coord) | 
|  | if pin_dir != Direction.NO_SIDE: | 
|  | conns[pin_dir] = idx | 
|  |  | 
|  | self.track_cache[coord] = conns | 
|  | return conns | 
|  |  | 
|  |  | 
|  | def main(): | 
|  | import doctest | 
|  |  | 
|  | print('Doctest begin') | 
|  | failure_count, test_count = doctest.testmod(optionflags=doctest.ELLIPSIS) | 
|  | assert test_count > 0 | 
|  | assert failure_count == 0, "Doctests failed!" | 
|  | print('Doctest end') | 
|  |  | 
|  |  | 
|  | if __name__ == "__main__": | 
|  | main() |