| #!/usr/bin/env python3 | |
| """ | |
| Functions for dealing with groups of points. | |
| """ | |
| import math | |
| import string | |
| import sys | |
| import enum | |
| from collections import defaultdict | |
| from collections import namedtuple | |
| from ..asserts import assert_eq | |
| from ..asserts import assert_type | |
| from ..rr_graph import Position, P | |
| _NamedPosition = namedtuple("NamedPosition", ["pos", "names"]) | |
| class NamedPosition(_NamedPosition): | |
| """Class to store a position and a set of names associated with it.""" | |
| def __new__(cls, pos, names): | |
| assert_type(pos, Position) | |
| assert_type(names, list) | |
| for n in names: | |
| assert_type(n, str) | |
| return _NamedPosition.__new__(cls, pos, names) | |
| @property | |
| def x(self): | |
| return self.pos.x | |
| @property | |
| def y(self): | |
| return self.pos.y | |
| @property | |
| def first(self): | |
| assert len(self.names) > 0, self.names | |
| return self.names[0] | |
| def __str__(self): | |
| return "NP({},{},{})".format( | |
| self.pos.x, | |
| self.pos.y, | |
| ",".join(repr(n) for n in self.names), | |
| ) | |
| def __repr__(self): | |
| return self.__str__() | |
| def NP(x, y, *n): | |
| """Syntactic sugar for NamedPosition.""" | |
| n = list(n) | |
| if not n: | |
| n = ["{}+{}".format(string.ascii_letters[x], string.ascii_letters[y])] | |
| return NamedPosition(P(x, y), n) | |
| class StraightSegment(list): | |
| class Type(enum.Enum): | |
| """Category of straight segment | |
| Vertical - all points have same x value | |
| Horizontal - all point have same y value | |
| Stub - single point, no direction yet. | |
| """ | |
| V = '|' | |
| V__doc__ = 'Vertical' | |
| H = '-' | |
| H__doc__ = 'Horizontal' | |
| S = 'o' | |
| S__doc__ = 'Stub' | |
| def __repr__(self): | |
| return 'StraightSegment.Type.' + self.name | |
| def __init__(self, direction, positions): | |
| list.__init__(self, positions) | |
| assert_type(direction, StraightSegment.Type) | |
| assert_type(positions, list) | |
| if direction == StraightSegment.Type.S: | |
| assert len(positions) == 1, ( | |
| "Stubs must only have one position not {}".format(positions) | |
| ) | |
| else: | |
| for p in positions: | |
| assert_type(p, (Position, NamedPosition)) | |
| if direction == StraightSegment.Type.V: | |
| assert_eq(p[0].x, p.x) | |
| elif direction == StraightSegment.Type.H: | |
| assert_eq(p[0].y, p.y) | |
| else: | |
| assert False, "Unknown direction {}".format(direction) | |
| self.direction = direction | |
| @property | |
| def d(self): | |
| return self.direction | |
| def __str__(self): | |
| return "{} {}".format(self.direction.value, list.__repr__(self)) | |
| def __repr__(self): | |
| return "{}({}, {})".format( | |
| self.__class__.__name__, | |
| self.direction, | |
| list.__repr__(self), | |
| ) | |
| def y_range(self): | |
| y_pos = [p.y for p in self] | |
| return min(y_pos), max(y_pos) | |
| def x_range(self): | |
| x_pos = [p.x for p in self] | |
| return min(x_pos), max(x_pos) | |
| def along(self, pos): | |
| """Return if position lies along segment""" | |
| assert len(self) > 0 | |
| assert_type(pos, (NamedPosition, Position)) | |
| if len(self) == 1: | |
| assert self.d == StraightSegment.Type.S, (self, pos) | |
| return (pos.x == self[0].x) or (pos.y == self[0].y) | |
| elif self.d == StraightSegment.Type.V: | |
| return pos.x == self[0].x | |
| elif self.d == StraightSegment.Type.H: | |
| return pos.y == self[0].y | |
| else: | |
| assert False, "Weird segment %s" % self | |
| def get_at(self, pos): | |
| for p in self: | |
| if p.x == pos.x and p.y == pos.y: | |
| return p | |
| raise ValueError("Nothing found at {} {}".format(pos, self)) | |
| def has_at(self, pos): | |
| try: | |
| self.get_at(pos) | |
| return True | |
| except ValueError: | |
| return False | |
| def replace(self, pos): | |
| """Replace point at same x and y. Essentially updating the names. | |
| If no match is found, list remains unmodified and the function returns False. | |
| """ | |
| for i, p in enumerate(self): | |
| if pos.x != p.x or pos.y != p.y: | |
| continue | |
| self[i] = pos | |
| return True | |
| return False | |
| def append(self, pos): | |
| """Add new position. Update direction if needed""" | |
| if len(self) > 0: | |
| if pos.x == self[0].x: | |
| direction = StraightSegment.Type.V | |
| elif pos.y == self[0].y: | |
| direction = StraightSegment.Type.H | |
| else: | |
| direction = self.direction | |
| if self.direction == StraightSegment.Type.S: | |
| assert len(self) <= 1, "Stub must have at most single point" | |
| self.direction = direction | |
| assert direction == self.direction, "Can't append in different direction {} {}".format( | |
| self.direction, direction | |
| ) | |
| list.append(self, pos) | |
| def extend_to(self, pos): | |
| """Return point starting at Segment first position and extend for connection to pos""" | |
| pclass = self[0].pos.__class__ | |
| if self.d == StraightSegment.Type.H: | |
| return pclass(pos.x, self[0].y) | |
| elif self.d == StraightSegment.Type.V: | |
| return pclass(self[0].x, pos.y) | |
| elif self.d == StraightSegment.Type.S: | |
| return pclass(self[0].x, pos.y) | |
| # assert pos.x == self[0].x or pos.y == self[0].y, (pos, self) | |
| # return pclass(*pos) | |
| assert self.d != StraightSegment.Type.S, self | |
| assert False, self | |
| @property | |
| def names(self): | |
| """Return list of all names of points along segment""" | |
| names = [] | |
| for npos in self: | |
| names.extend(n for n in npos.names) | |
| return list(set(names)) | |
| def straight_longest(positions): | |
| """Get the largest straight section from a group of positions. | |
| Arguments: | |
| positions : [(Position, [str, ...]), ...] | |
| Returns: | |
| (StraightSegment([Position|NamedPosition, ...]), [Position|NamedPosition, ...]) | |
| >>> s, r = straight_longest([P(1,0),]) | |
| >>> print(str(s)) | |
| o [P(x=1, y=0)] | |
| >>> s, r = straight_longest([ | |
| ... P(1,0), | |
| ... P(0,1), P(1,1), P(2, 1), P(3, 1), | |
| ... P(0,2), P(1,2), P(2, 2), P(3, 2), | |
| ... P(1,3), | |
| ... ]) | |
| >>> print(str(s)) | |
| - [P(x=0, y=2), P(x=1, y=2), P(x=2, y=2), P(x=3, y=2)] | |
| >>> s, r = straight_longest(r) | |
| >>> print(str(s)) | |
| - [P(x=0, y=1), P(x=1, y=1), P(x=2, y=1), P(x=3, y=1)] | |
| >>> s, r = straight_longest(r) | |
| >>> print(str(s)) | |
| | [P(x=1, y=0), P(x=1, y=3)] | |
| >>> r | |
| [] | |
| >>> s, r = straight_longest([ | |
| ... P(1,0), | |
| ... P(0,1), P(1,1), P(2, 1), | |
| ... P(1,2), | |
| ... ]) | |
| >>> print(str(s)) | |
| - [P(x=0, y=1), P(x=1, y=1), P(x=2, y=1)] | |
| >>> s, r = straight_longest(r) | |
| >>> print(str(s)) | |
| | [P(x=1, y=0), P(x=1, y=2)] | |
| >>> r | |
| [] | |
| >>> s, r = straight_longest([ | |
| ... P(1,0), | |
| ... P(0,1), P(10, 1), | |
| ... P(1,5), | |
| ... ]) | |
| >>> print(str(s)) | |
| - [P(x=0, y=1), P(x=10, y=1)] | |
| >>> s, r = straight_longest(r) | |
| >>> print(str(s)) | |
| | [P(x=1, y=0), P(x=1, y=5)] | |
| >>> r | |
| [] | |
| >>> s, r = straight_longest([ | |
| ... P(0,0), P(1,0), | |
| ... P(0,1), P(2, 1), | |
| ... P(0,2), P(1,2), | |
| ... P(0,3), P(1,3), P(2, 3), | |
| ... P(0,4), P(1,4), | |
| ... P(0,5), P(2, 5), | |
| ... P(0,6), P(1,6), | |
| ... ]) | |
| >>> print(str(s)) | |
| | [P(x=0, y=0), P(x=0, y=1), P(x=0, y=2), P(x=0, y=3), P(x=0, y=4), P(x=0, y=5), P(x=0, y=6)] | |
| >>> s, r = straight_longest(r) | |
| >>> print(str(s)) | |
| | [P(x=1, y=0), P(x=1, y=2), P(x=1, y=3), P(x=1, y=4), P(x=1, y=6)] | |
| >>> s, r = straight_longest(r) | |
| >>> print(str(s)) | |
| | [P(x=2, y=1), P(x=2, y=3), P(x=2, y=5)] | |
| >>> r | |
| [] | |
| """ | |
| assert_type(positions, list) | |
| # Count how frequently each x and y coordinate is found. | |
| coordinate_count = { | |
| 'x': defaultdict(int), | |
| 'y': defaultdict(int), | |
| } | |
| for pos in positions: | |
| assert_type(pos, (Position, NamedPosition)) | |
| coordinate_count['x'][pos.x] += 1 | |
| coordinate_count['y'][pos.y] += 1 | |
| # If out the most common x and v coordinates | |
| x_val = list(sorted((c, x) for x, c in coordinate_count['x'].items())) | |
| y_val = list(sorted((c, y) for y, c in coordinate_count['y'].items())) | |
| # Work out the direction this segment will run | |
| if x_val[-1][0] > y_val[-1][0]: | |
| direction = StraightSegment.Type.V | |
| else: | |
| direction = StraightSegment.Type.H | |
| # Work out which positions are on this segment | |
| segment = StraightSegment(direction, []) | |
| not_in_segment = [] | |
| if direction == StraightSegment.Type.V: | |
| x_pos = x_val[-1][1] | |
| for pos in positions: | |
| if pos.x != x_pos: | |
| not_in_segment.append(pos) | |
| continue | |
| segment.append(pos) | |
| elif direction == StraightSegment.Type.H: | |
| direction = "-" | |
| y_pos = y_val[-1][1] | |
| for pos in positions: | |
| if pos.y != y_pos: | |
| not_in_segment.append(pos) | |
| continue | |
| segment.append(pos) | |
| # Single length segments are "stubs" | |
| if len(segment) == 1: | |
| segment.direction = StraightSegment.Type.S | |
| return segment, not_in_segment | |
| def print_segments(segs): | |
| for s in segs: | |
| print(s) | |
| def print_conns(conns): | |
| for p, joins in sorted(conns.items()): | |
| for (aname, bname) in joins: | |
| print("{}x{} {}<->{}".format(p.x, p.y, aname, bname)) | |
| def decompose_into_straight_lines(positions): | |
| """ | |
| Takes a network and converts it to a list of straight segments. | |
| Arguments: | |
| positions : [(Position, [str, ...]), ...] | |
| >>> # Single element | |
| >>> pos = [NP(1,0),] | |
| >>> conns, segs = decompose_into_straight_lines(pos) | |
| >>> for s in segs: | |
| ... print(s) | |
| o [NP(1,0,'b+a')] | |
| >>> # Single horizontal line | |
| >>> pos = [NP(1,0), NP(2,0)] | |
| >>> conns, segs = decompose_into_straight_lines(pos) | |
| >>> for s in segs: | |
| ... print(s) | |
| - [NP(1,0,'b+a'), NP(2,0,'c+a')] | |
| >>> # Single vertical line | |
| >>> pos = [NP(1,2), NP(1,3)] | |
| >>> conns, segs = decompose_into_straight_lines(pos) | |
| >>> for s in segs: | |
| ... print(s) | |
| | [NP(1,2,'b+c'), NP(1,3,'b+d')] | |
| >>> # Cross shape | |
| >>> pos = [ | |
| ... NP(1,0), | |
| ... NP(0,1), NP(1,1), NP(2, 1), | |
| ... NP(1,2), | |
| ... ] | |
| >>> conns, segs = decompose_into_straight_lines(pos) | |
| >>> print_segments(segs) | |
| - [NP(0,1,'a+b'), NP(1,1,'b+b'), NP(2,1,'c+b')] | |
| | [NP(1,0,'b+a'), NP(1,1,'b+b_x'), NP(1,2,'b+c')] | |
| >>> print_conns(conns) | |
| 1x1 b+b<->b+b_x | |
| >>> # Cross with two horizontal bars | |
| >>> pos = [ | |
| ... NP(1,0), | |
| ... NP(0,1), NP(1,1), NP(2, 1), NP(3, 1), | |
| ... NP(0,2), NP(1,2), NP(2, 2), NP(3, 2), | |
| ... NP(1,3), | |
| ... ] | |
| >>> conns, segs = decompose_into_straight_lines(pos) | |
| >>> print_segments(segs) | |
| - [NP(0,1,'a+b'), NP(1,1,'b+b'), NP(2,1,'c+b'), NP(3,1,'d+b')] | |
| - [NP(0,2,'a+c'), NP(1,2,'b+c'), NP(2,2,'c+c'), NP(3,2,'d+c')] | |
| | [NP(1,0,'b+a'), NP(1,1,'b+b_x'), NP(1,2,'b+c_x'), NP(1,3,'b+d')] | |
| >>> print_conns(conns) | |
| 1x1 b+b<->b+b_x | |
| 1x2 b+c<->b+c_x | |
| >>> # Cross with unequal horizontal bars | |
| >>> pos = [ | |
| ... NP(1,0), | |
| ... NP(0,1), NP(1,1), NP(2, 1), NP(3, 1), | |
| ... NP(0,2), NP(1,2), NP(2, 2), | |
| ... NP(1,3), | |
| ... ] | |
| >>> conns, segs = decompose_into_straight_lines(pos) | |
| >>> print_segments(segs) | |
| - [NP(0,1,'a+b'), NP(1,1,'b+b'), NP(2,1,'c+b'), NP(3,1,'d+b')] | |
| - [NP(0,2,'a+c'), NP(1,2,'b+c'), NP(2,2,'c+c')] | |
| | [NP(1,0,'b+a'), NP(1,1,'b+b_x'), NP(1,2,'b+c_x'), NP(1,3,'b+d')] | |
| >>> print_conns(conns) | |
| 1x1 b+b<->b+b_x | |
| 1x2 b+c<->b+c_x | |
| >>> # FIXME: Cross with missing middle | |
| >>> pos = [ | |
| ... NP(1,0), | |
| ... NP(0,1), NP(10, 1), | |
| ... NP(1,5), | |
| ... ] | |
| >>> try: | |
| ... conns, segs = decompose_into_straight_lines(pos) | |
| ... except AssertionError: | |
| ... pass | |
| >>> # 3 straight lines | |
| >>> pos = [ | |
| ... NP(0,0), NP(1,0), | |
| ... NP(0,1), NP(2,1), | |
| ... NP(0,2), NP(1,2), | |
| ... NP(0,3), NP(1,3), NP(2,3), | |
| ... NP(0,4), NP(1,4), | |
| ... NP(0,5), NP(2,5), | |
| ... NP(0,6), NP(1,6), | |
| ... ] | |
| >>> conns, segs = decompose_into_straight_lines(pos) | |
| >>> print_segments(segs) | |
| | [NP(0,0,'a+a'), NP(0,1,'a+b'), NP(0,2,'a+c'), NP(0,3,'a+d'), NP(0,4,'a+e'), NP(0,5,'a+f'), NP(0,6,'a+g')] | |
| - [NP(0,3,'a+d_x'), NP(1,3,'b+d_x'), NP(2,3,'c+d_x')] | |
| | [NP(1,0,'b+a'), NP(1,2,'b+c'), NP(1,3,'b+d'), NP(1,4,'b+e'), NP(1,6,'b+g')] | |
| | [NP(2,1,'c+b'), NP(2,3,'c+d'), NP(2,5,'c+f')] | |
| >>> print_conns(conns) | |
| 0x3 a+d<->a+d_x | |
| 1x3 b+d<->b+d_x | |
| 2x3 c+d<->c+d_x | |
| >>> # H shaped | |
| >>> pos = [ | |
| ... NP(0,0), NP(2,0), | |
| ... NP(0,1), NP(2,1), | |
| ... NP(0,2), NP(2,2), | |
| ... NP(0,3), NP(1,3), NP(2,3), | |
| ... NP(0,4), NP(2,4), | |
| ... ] | |
| >>> conns, segs = decompose_into_straight_lines(pos) | |
| >>> print_segments(segs) | |
| | [NP(0,0,'a+a'), NP(0,1,'a+b'), NP(0,2,'a+c'), NP(0,3,'a+d'), NP(0,4,'a+e')] | |
| - [NP(0,3,'a+d_x'), NP(1,3,'b+d'), NP(2,3,'c+d_x')] | |
| | [NP(2,0,'c+a'), NP(2,1,'c+b'), NP(2,2,'c+c'), NP(2,3,'c+d'), NP(2,4,'c+e')] | |
| >>> print_conns(conns) | |
| 0x3 a+d<->a+d_x | |
| 2x3 c+d<->c+d_x | |
| >>> # Corner shape | |
| >>> pos = [ | |
| ... NP(0,1,'io_0/D_IN_0'), NP(1,1,'neigh_op_lft_0','neigh_op_lft_4'), | |
| ... NP(1,2,'neigh_op_bnl_0','neigh_op_bnl_4'), | |
| ... ] | |
| >>> conns, segs = decompose_into_straight_lines(pos) | |
| >>> print_segments(segs) | |
| - [NP(0,1,'io_0/D_IN_0'), NP(1,1,'neigh_op_lft_0','neigh_op_lft_4')] | |
| | [NP(1,1,'neigh_op_lft_0_x'), NP(1,2,'neigh_op_bnl_0','neigh_op_bnl_4')] | |
| >>> print_conns(conns) | |
| 1x1 neigh_op_lft_0<->neigh_op_lft_0_x | |
| >>> # Going around corners | |
| >>> pos = [ | |
| ... NP(1,0,'span4_horz_r_4'), NP(2,0,'span4_horz_r_8'), NP(3,0,'span4_horz_r_12'), NP(4,0,'span4_horz_l_12'), | |
| ... NP(0,1,'span4_vert_b_0'), | |
| ... ] | |
| >>> conns, segs = decompose_into_straight_lines(pos) | |
| >>> print_segments(segs) | |
| - [NP(0,0,'span4_horz_r_4_to_span4_vert_b_0'), NP(1,0,'span4_horz_r_4'), NP(2,0,'span4_horz_r_8'), NP(3,0,'span4_horz_r_12'), NP(4,0,'span4_horz_l_12')] | |
| | [NP(0,0,'span4_horz_r_4_to_span4_vert_b_0_x'), NP(0,1,'span4_vert_b_0')] | |
| >>> print_conns(conns) | |
| 0x0 span4_horz_r_4_to_span4_vert_b_0<->span4_horz_r_4_to_span4_vert_b_0_x | |
| >>> pos = [ | |
| ... NP(1,0,'span4_horz_r_4'), NP(2,0,'span4_horz_r_8'), NP(3,0,'span4_horz_r_12'), | |
| ... NP(0,1,'span4_vert_b_0'), | |
| ... NP(0,2,'span4_vert_b_0'), | |
| ... ] | |
| >>> conns, segs = decompose_into_straight_lines(pos) | |
| >>> print_segments(segs) | |
| - [NP(0,0,'span4_horz_r_4_to_span4_vert_b_0'), NP(1,0,'span4_horz_r_4'), NP(2,0,'span4_horz_r_8'), NP(3,0,'span4_horz_r_12')] | |
| | [NP(0,0,'span4_horz_r_4_to_span4_vert_b_0_x'), NP(0,1,'span4_vert_b_0'), NP(0,2,'span4_vert_b_0')] | |
| >>> print_conns(conns) | |
| 0x0 span4_horz_r_4_to_span4_vert_b_0<->span4_horz_r_4_to_span4_vert_b_0_x | |
| >>> pos = [ | |
| ... NP(10,0,'span4_horz_r_4'), NP(12,0,'span4_horz_r_8'), NP(13,0,'span4_horz_r_12'), | |
| ... NP(14,1,'span4_vert_b_0'), | |
| ... NP(14,2,'span4_vert_b_0'), | |
| ... ] | |
| >>> conns, segs = decompose_into_straight_lines(pos) | |
| >>> print_segments(segs) | |
| - [NP(10,0,'span4_horz_r_4'), NP(12,0,'span4_horz_r_8'), NP(13,0,'span4_horz_r_12'), NP(14,0,'span4_horz_r_12_to_span4_vert_b_0')] | |
| | [NP(14,0,'span4_horz_r_12_to_span4_vert_b_0_x'), NP(14,1,'span4_vert_b_0'), NP(14,2,'span4_vert_b_0')] | |
| >>> print_conns(conns) | |
| 14x0 span4_horz_r_12_to_span4_vert_b_0<->span4_horz_r_12_to_span4_vert_b_0_x | |
| >>> # FIXME: This is a bit weird | |
| >>> # Make sure NP(1,2) isn't in the output... | |
| >>> pos = [ | |
| ... NP(0,0), NP(2,0), | |
| ... NP(0,1), NP(1,1), NP(2,1), | |
| ... NP(0,2), NP(2,2), | |
| ... NP(0,3), NP(1,3), NP(2,3), | |
| ... NP(0,4), NP(2,4), | |
| ... ] | |
| >>> conns, segs = decompose_into_straight_lines(pos) | |
| >>> print_segments(segs) | |
| | [NP(0,0,'a+a'), NP(0,1,'a+b'), NP(0,2,'a+c'), NP(0,3,'a+d'), NP(0,4,'a+e')] | |
| - [NP(0,3,'a+d_x'), NP(1,3,'b+d_x'), NP(2,3,'c+d_x')] | |
| | [NP(1,1,'b+b'), NP(1,3,'b+d')] | |
| | [NP(2,0,'c+a'), NP(2,1,'c+b'), NP(2,2,'c+c'), NP(2,3,'c+d'), NP(2,4,'c+e')] | |
| >>> print_conns(conns) | |
| 0x3 a+d<->a+d_x | |
| 1x3 b+d<->b+d_x | |
| 2x3 c+d<->c+d_x | |
| >>> # FIXME: This is weird? | |
| >>> pos = [ | |
| ... NP(0,16,'span4_vert_t_12'), | |
| ... NP(1,17,'span4_horz_l_12'), | |
| ... ] | |
| >>> conns, segs = decompose_into_straight_lines(pos) | |
| >>> print_segments(segs) | |
| - [NP(0,16,'span4_vert_t_12'), NP(1,16,'span4_horz_l_12_to_span4_vert_t_12_x')] | |
| | [NP(1,16,'span4_horz_l_12_to_span4_vert_t_12'), NP(1,17,'span4_horz_l_12')] | |
| >>> print_conns(conns) | |
| 1x16 span4_horz_l_12_to_span4_vert_t_12<->span4_horz_l_12_to_span4_vert_t_12_x | |
| """ # noqa: 501 | |
| assert len(positions) > 0, positions | |
| for pos in positions: | |
| assert_type(pos, NamedPosition) | |
| npclass = positions[0].__class__ | |
| pclass = positions[0].pos.__class__ | |
| # position_used_by[position] = (NamedPosition, StraightSegment) | |
| position_used_by = {} | |
| segments = { | |
| '|': [], | |
| '-': [], | |
| 'o': [], | |
| 'ALL': [], | |
| } | |
| # connections[position] = (namea, nameb) | |
| connections = defaultdict(list) | |
| def add_segment(s, is_spine=False): | |
| assert len(s) > 0, (s, is_spine) | |
| assert_type(s, StraightSegment) | |
| for np in s: | |
| assert_type(np, NamedPosition) | |
| if not is_spine: | |
| assert np.pos not in position_used_by, ( | |
| np.pos, s, position_used_by[np.pos] | |
| ) | |
| position_used_by[np.pos] = (np, s) | |
| for p, (other_p, other_s) in position_used_by.items(): | |
| assert p.x == other_p.x, (p, other_p) | |
| assert p.y == other_p.y, (p, other_p) | |
| if s is other_s: | |
| continue | |
| if not s.along(p): | |
| continue | |
| assert p not in connections, (p, connections) | |
| current_name = other_p.names[0] | |
| new_name = current_name + '_x' | |
| connection_p = npclass(p, [new_name]) | |
| if is_spine: | |
| s.replace(connection_p) | |
| else: | |
| s.append(connection_p) | |
| connections[p].append((current_name, new_name)) | |
| s.sort() | |
| # print(s) | |
| segments['ALL'].append(s) | |
| segments[s.d.value].append(s) | |
| # Convert the positions into straight lines. | |
| remaining = list(positions) | |
| while len(remaining) > 0: | |
| assert_type(remaining, list) | |
| straight_segment, remaining = straight_longest(remaining) | |
| add_segment(straight_segment) | |
| if len(segments['ALL']) == 1: | |
| assert len(connections) == 0, (segments, connections) | |
| return connections, segments['ALL'] | |
| # FIXME: Hack for dealing with the corner case | |
| if len(segments['ALL']) == 2 and len(connections) == 0: | |
| longest = segments['ALL'][0] | |
| other = [] | |
| for seg in segments['ALL'][1:]: | |
| if len(longest) < len(seg): | |
| longest, seg = seg, longest | |
| other.append(seg) | |
| # assert len(other) == 1, (longest, other) | |
| # assert len(longest) > 1, (longest, other) | |
| pointa, pointb = straight_closet(longest, other[0]) | |
| corner_point = longest.extend_to(pointb) | |
| corner_name = "{}_to_{}".format(pointa.names[0], pointb.names[0]) | |
| longest.append(npclass(corner_point, [corner_name])) | |
| longest.sort() | |
| other[0].append(npclass(corner_point, [corner_name + "_x"])) | |
| other[0].sort() | |
| connections[corner_point].append((corner_name, corner_name + "_x")) | |
| """ | |
| # FIXME: Check all the segments are connected together | |
| for_checking = list(segments['ALL']) | |
| connected = [for_checking.pop(0)] | |
| while len(for_checking) > 0: | |
| point = None | |
| for seg1 in for_checking: | |
| for seg2 in connected: | |
| test_point = common_point(seg1, seg2) | |
| if test_point is not None: | |
| point = test_point | |
| connected.append(seg2) | |
| break | |
| if point is not None: | |
| for_checking.remove(connected[-1]) | |
| else: | |
| assert False, (for_checking, connected) | |
| """ | |
| # Check connections | |
| # If they are all vertical or horizontal, we need to create a spine. | |
| if len(connections) == 0 and len(segments['-']) > 0: | |
| assert len(segments['|']) == 0 | |
| for seg in segments['o']: | |
| seg.direction = StraightSegment.Type.H | |
| segments['-'].append(seg) | |
| x_pos = defaultdict(int) | |
| for seg in segments['-']: | |
| assert len(seg) > 0, seg | |
| for p in seg: | |
| x_pos[p.x] += 1 | |
| x = list(sorted((m, x) for x, m in x_pos.items()))[-1][-1] | |
| spine = StraightSegment(StraightSegment.Type.V, []) | |
| for seg in segments['-']: | |
| p = pclass(x, seg.y_range()[0]) | |
| spine.append(npclass(p, names=[seg.get_at(p).first])) | |
| add_segment(spine, True) | |
| elif len(connections) == 0 and len(segments['|']) > 0: | |
| assert len(segments['-']) == 0 | |
| for seg in segments['o']: | |
| seg.direction = StraightSegment.Type.V | |
| segments['|'].append(seg) | |
| y_pos = defaultdict(int) | |
| for seg in segments['|']: | |
| assert len(seg) > 0, seg | |
| for p in seg: | |
| y_pos[p.y] += 1 | |
| y = list(sorted((m, y) for y, m in y_pos.items()))[-1][-1] | |
| spine = StraightSegment(StraightSegment.Type.H, []) | |
| for seg in segments['|']: | |
| assert len(seg) > 0, seg | |
| p = pclass(seg.x_range()[0], y) | |
| spine.append(npclass(p, names=[seg.get_at(p).first])) | |
| add_segment(spine, True) | |
| segments['ALL'].sort() | |
| return connections, segments['ALL'] | |
| def straight_ends(positions): | |
| """Get the ends of a straight line. | |
| >>> straight_ends([P(0,1), P(1,1)]) | |
| (P(x=0, y=1), P(x=1, y=1)) | |
| >>> straight_ends([P(0,1), P(1,1), P(2,1)]) | |
| (P(x=0, y=1), P(x=2, y=1)) | |
| >>> straight_ends([P(1,0), P(1,1), P(1,2)]) | |
| (P(x=1, y=0), P(x=1, y=2)) | |
| >>> straight_ends([P(1,1), P(1,1)]) | |
| (P(x=1, y=1), P(x=1, y=1)) | |
| >>> # FIXME: Is this the correct behaviour? | |
| >>> straight_ends([P(1,1), P(1,4)]) | |
| (P(x=1, y=1), P(x=1, y=4)) | |
| >>> straight_ends([P(0,0), P(1,1)]) | |
| Traceback (most recent call last): | |
| ... | |
| TypeError: Not straight x:{0, 1} y:{0, 1} | |
| >>> straight_ends([P(0,0), P(1,1), P(0,2)]) | |
| Traceback (most recent call last): | |
| ... | |
| TypeError: Not straight x:{0, 1} y:{0, 1, 2} | |
| """ | |
| pclass = positions[0].__class__ | |
| x_pos = set(p.x for p in positions) | |
| y_pos = set(p.y for p in positions) | |
| if len(x_pos) > 1: | |
| if len(y_pos) != 1: | |
| raise TypeError("Not straight x:{} y:{}".format(x_pos, y_pos)) | |
| if len(y_pos) > 1: | |
| if len(x_pos) != 1: | |
| raise TypeError("Not straight x:{} y:{}".format(x_pos, y_pos)) | |
| start = pclass(min(x_pos), min(y_pos)) | |
| end = pclass(max(x_pos), max(y_pos)) | |
| assert start.x == end.x or start.y == end.y, "{} {}".format(start, end) | |
| return start, end | |
| def distance(p1, p2): | |
| return math.sqrt((p2.x - p1.x)**2 + (p2.y - p1.y)**2) | |
| def straight_closet(line1, line2): | |
| """ | |
| # >>> straight_intersection((P(0, 0), P(0, 10)), (P(0, 0), P(10, 0))) | |
| # P(x=0, y=0) | |
| # >>> straight_intersection((P(10, 0), P(10, 10)), (P(0, 0), P(10, 0))) | |
| # P(x=10, y=0) | |
| # >>> straight_intersection((P(5, 0), P(5, 10)), (P(0, 0), P(10, 0))) | |
| # P(x=5, y=0) | |
| # >>> straight_intersection((P(0, 5), P(10, 5)), (P(5, 0), P(5, 10))) | |
| # P(x=5, y=5) | |
| """ | |
| min_d = sys.maxsize | |
| pa, pb = None, None | |
| for p1 in line1: | |
| for p2 in line2: | |
| d = distance(p1, p2) | |
| if d < min_d: | |
| pa, pb = p1, p2 | |
| min_d = d | |
| assert pa is not None, "{} {}".format(line1, line2) | |
| assert pb is not None, "{} {}".format(line1, line2) | |
| return pa, pb | |
| class Point(object): | |
| def __init__(self, coord): | |
| self.x, self.y = coord | |
| self.tracks = 0 | |
| def __repr__(self): | |
| return 'Point(coord=({},{}),tracks={})'.format( | |
| self.x, self.y, repr(self.tracks) | |
| ) | |
| class Track(object): | |
| def __init__(self, dim, tracks=None, other_tracks=None, points=[]): | |
| self.dim = dim | |
| self.points = [] | |
| self.tracks = tracks | |
| self.other_tracks = other_tracks | |
| for p in points: | |
| self.add_point(p) | |
| def add_point(self, p): | |
| self.points.append(p) | |
| p.tracks += 1 | |
| def __repr__(self): | |
| return 'Track(dim={},points={})'.format( | |
| repr(self.dim), repr(self.points) | |
| ) | |
| def decompose_points_into_tracks( | |
| points, grid_width=None, grid_height=None, right_only=False | |
| ): | |
| """ This function takes a bag of points and returns a set of x lines and | |
| y lines that cover all points, and all lines touch each other. | |
| This is the first step to forming VPR tracks from points. VPR tracks have | |
| limited length, whereas this function returns lines of infinite length. | |
| Args: | |
| points: List of (x, y) tuples that are points to be connected into the | |
| the track | |
| grid_width (int): Optional maximum x dimension | |
| grid_height (int): Optional maximum y dimension | |
| right_only (bool): Assume that points are only available via pins on | |
| the right. Some arches restrict pins to the right side only. | |
| >>> # Single element | |
| >>> pos = [(1,0)] | |
| >>> ret = decompose_points_into_tracks(pos) | |
| >>> print_tracks(ret) | |
| x = [] | |
| y = [0] | |
| >>> # Single horizontal line | |
| >>> pos = [(1,0), (2,0)] | |
| >>> ret = decompose_points_into_tracks(pos) | |
| >>> print_tracks(ret) | |
| x = [] | |
| y = [0] | |
| >>> # Single vertical line | |
| >>> pos = [(1,2), (1,3)] | |
| >>> ret = decompose_points_into_tracks(pos) | |
| >>> print_tracks(ret) | |
| x = [1] | |
| y = [] | |
| >>> # Cross shape | |
| >>> pos = [ | |
| ... (2,1), | |
| ... (1,2), (2,2), (3, 2), | |
| ... (2,3), | |
| ... ] | |
| >>> ret = decompose_points_into_tracks(pos) | |
| >>> print_tracks(ret) | |
| x = [2] | |
| y = [2] | |
| >>> # Cross shape at edge | |
| >>> pos = [ | |
| ... (1,0), | |
| ... (0,1), (1,1), (2, 1), | |
| ... (1,2), | |
| ... ] | |
| >>> ret = decompose_points_into_tracks(pos) | |
| >>> print_tracks(ret) | |
| x = [0] | |
| y = [0] | |
| >>> # Cross with two horizontal bars | |
| >>> pos = [ | |
| ... (1,0), | |
| ... (0,1), (1,1), (2, 1), (3, 1), | |
| ... (0,2), (1,2), (2, 2), (3, 2), | |
| ... (1,3), | |
| ... ] | |
| >>> ret = decompose_points_into_tracks(pos) | |
| >>> print_tracks(ret) | |
| x = [0] | |
| y = [0, 1] | |
| >>> # Cross with unequal horizontal bars | |
| >>> pos = [ | |
| ... (1,0), | |
| ... (0,1), (1,1), (2, 1), (3, 1), | |
| ... (0,2), (1,2), (2, 2), | |
| ... (1,3), | |
| ... ] | |
| >>> ret = decompose_points_into_tracks(pos) | |
| >>> print_tracks(ret) | |
| x = [0] | |
| y = [0, 1] | |
| >>> pos = [ | |
| ... (1,0), | |
| ... (0,1), (10, 1), | |
| ... (1,5), | |
| ... ] | |
| >>> ret = decompose_points_into_tracks(pos) | |
| >>> print_tracks(ret) | |
| x = [0] | |
| y = [0] | |
| >>> # 3 straight lines | |
| >>> pos = [ | |
| ... (1,0), | |
| ... (0,1), (2,1), | |
| ... (0,2), (1,2), | |
| ... (0,3), (1,3), (2,3), | |
| ... (0,4), (1,4), | |
| ... (0,5), (2,5), | |
| ... (0,6), (1,6), | |
| ... ] | |
| >>> ret = decompose_points_into_tracks(pos) | |
| >>> print_tracks(ret) | |
| x = [0] | |
| y = [0, 3, 5] | |
| >>> # H shaped | |
| >>> pos = [ | |
| ... (2,0), | |
| ... (0,1), (2,1), | |
| ... (0,2), (2,2), | |
| ... (0,3), (1,3), (2,3), | |
| ... (0,4), (2,4), | |
| ... ] | |
| >>> ret = decompose_points_into_tracks(pos) | |
| >>> print_tracks(ret) | |
| x = [0] | |
| y = [0, 2, 3] | |
| >>> # H shaped | |
| >>> pos = [ | |
| ... (1,1), (3,1), | |
| ... (1,2), (3,2), | |
| ... (1,3), (3,3), | |
| ... (1,4), (2,4), (3,4), | |
| ... (1,5), (3,5), | |
| ... ] | |
| >>> ret = decompose_points_into_tracks(pos) | |
| >>> print_tracks(ret) | |
| x = [1, 2] | |
| y = [4] | |
| >>> # Corner shape | |
| >>> pos = [ | |
| ... (0,1), (1,1), | |
| ... (1,2) | |
| ... ] | |
| >>> ret = decompose_points_into_tracks(pos) | |
| >>> print_tracks(ret) | |
| x = [0] | |
| y = [] | |
| >>> # Going around corners | |
| >>> pos = [ | |
| ... (1,0), (2,0), (3,0), (4,0), | |
| ... (0,1), | |
| ... ] | |
| >>> ret = decompose_points_into_tracks(pos) | |
| >>> print_tracks(ret) | |
| x = [0] | |
| y = [0] | |
| >>> pos = [ | |
| ... (1,0), (2,0), (3,0), | |
| ... (0,1), | |
| ... (0,2), | |
| ... ] | |
| >>> ret = decompose_points_into_tracks(pos) | |
| >>> print_tracks(ret) | |
| x = [0] | |
| y = [0] | |
| >>> pos = [ | |
| ... (10,0), (12,0), (13,0), | |
| ... (14,1), | |
| ... (14,2), | |
| ... ] | |
| >>> ret = decompose_points_into_tracks(pos) | |
| >>> print_tracks(ret) | |
| x = [14] | |
| y = [0] | |
| >>> # Make sure (1,2) isn't in the output... | |
| >>> pos = [ | |
| ... (2,0), | |
| ... (0,1), (1,1), (2,1), | |
| ... (0,2), (2,2), | |
| ... (0,3), (1,3), (2,3), | |
| ... (0,4), (2,4), | |
| ... ] | |
| >>> ret = decompose_points_into_tracks(pos) | |
| >>> print_tracks(ret) | |
| x = [0] | |
| y = [0, 2, 3] | |
| >>> pos = [ | |
| ... (0,16), | |
| ... (1,17), | |
| ... ] | |
| >>> ret = decompose_points_into_tracks(pos) | |
| >>> print_tracks(ret) | |
| x = [0] | |
| y = [] | |
| >>> pos = [ | |
| ... (68,48), (69,48), | |
| ... (68,49), (69,49), | |
| ... (69,50), | |
| ... (69,51), | |
| ... (69,52), | |
| ... (69,53), (70,53), (71,53), (72,53)] | |
| >>> ret = decompose_points_into_tracks(pos) | |
| >>> print_tracks(ret) | |
| x = [68] | |
| y = [52] | |
| """ | |
| xs, ys = zip(*points) | |
| x_min = max(0, min(xs) - 1) | |
| x_max = max(xs) | |
| if grid_width is not None: | |
| x_max = min(x_max, grid_width - 2) | |
| y_min = max(0, min(ys) - 1) | |
| y_max = max(ys) | |
| if grid_height is not None: | |
| y_max = min(y_max, grid_height - 2) | |
| points = [Point(p) for p in points] | |
| x_tracks = {} | |
| y_tracks = {} | |
| for x in range(x_min, x_max + 1): | |
| x_tracks[x] = Track(dim=x, tracks=x_tracks, other_tracks=y_tracks) | |
| for y in range(y_min, y_max + 1): | |
| y_tracks[y] = Track(dim=y, tracks=y_tracks, other_tracks=x_tracks) | |
| def on_x_track(p): | |
| # x tracks extend from 1 to grid_height - 1 | |
| if p.y <= 0: | |
| return False | |
| if grid_height is not None and p.y >= grid_height - 2: | |
| return False | |
| return True | |
| def on_y_track(p): | |
| # y tracks extend from 1 to grid_width - 1 | |
| if p.x <= 0: | |
| return False | |
| if grid_width is not None and p.x >= grid_width - 2: | |
| return False | |
| return True | |
| def is_corner_point(p): | |
| if p.x == 0 and p.y == 0: | |
| return True | |
| if grid_width is not None: | |
| assert grid_height is not None | |
| if p.x == grid_width - 1 and p.y == 0: | |
| return True | |
| if p.x == 0 and p.y == grid_height - 1: | |
| return True | |
| if p.x == grid_width - 1 and p.y == grid_height - 1: | |
| return True | |
| return False | |
| for p in points: | |
| # No points in corner | |
| assert not is_corner_point(p), p | |
| if on_x_track(p) and p.x in x_tracks: | |
| # The x-1 connection is for left pins. | |
| if p.x > 0 and not right_only: | |
| x_tracks[p.x - 1].add_point(p) | |
| x_tracks[p.x].add_point(p) | |
| # If all pins are on the right, then the y_tracks are used for cross | |
| # bar only, and points are not connected. | |
| if on_y_track(p) and not right_only and p.y in y_tracks: | |
| if p.y > 0: | |
| y_tracks[p.y - 1].add_point(p) | |
| y_tracks[p.y].add_point(p) | |
| def try_remove_track(track): | |
| assert track.dim in track.tracks | |
| for point in track.tracks[track.dim].points: | |
| if point.tracks <= 1: | |
| return False | |
| # If there is more than 1 other track, cannot have zero of this track. | |
| if len(track.tracks) <= 1 and len(track.other_tracks) > 1: | |
| return False | |
| for point in track.tracks[track.dim].points: | |
| point.tracks -= 1 | |
| del track.tracks[track.dim] | |
| return True | |
| # Attempt to remove tracks, starting with the track with the smallest | |
| # number of connections. | |
| while len(x_tracks) > 0 and len(y_tracks) > 0: | |
| x_track = min(x_tracks.values(), key=lambda key: len(key.points)) | |
| y_track = min(y_tracks.values(), key=lambda key: len(key.points)) | |
| if len(x_track.points) < len(y_track.points): | |
| if not try_remove_track(x_track): | |
| if not try_remove_track(y_track): | |
| break | |
| else: | |
| if not try_remove_track(y_track): | |
| if not try_remove_track(x_track): | |
| break | |
| # Walk each dimension, attempting to removing excess lines from the line | |
| # with the smallest number of points. | |
| while len(x_tracks) > 0: | |
| x_track = min(x_tracks.values(), key=lambda key: len(key.points)) | |
| if not try_remove_track(x_track): | |
| break | |
| while len(y_tracks) > 0: | |
| y_track = min(y_tracks.values(), key=lambda key: len(key.points)) | |
| if not try_remove_track(y_track): | |
| break | |
| # Walk each dimension, attempting to removing excess lines from any line, | |
| # starting with the smallest. | |
| while True: | |
| for x_track in sorted(x_tracks.values(), | |
| key=lambda key: len(key.points)): | |
| if try_remove_track(x_track): | |
| continue | |
| for y_track in sorted(y_tracks.values(), | |
| key=lambda key: len(key.points)): | |
| if try_remove_track(y_track): | |
| continue | |
| break | |
| # Sanity check results | |
| for p in points: | |
| on_a_track = False | |
| if on_x_track(p): | |
| if p.x > 0: | |
| on_a_track = on_a_track or p.x - 1 in x_tracks | |
| on_a_track = on_a_track or p.x in x_tracks | |
| if on_y_track(p): | |
| if p.y > 0: | |
| on_a_track = on_a_track or p.y - 1 in y_tracks | |
| on_a_track = on_a_track or p.y in y_tracks | |
| assert on_a_track, p | |
| return list(x_tracks.keys()), list(y_tracks.keys()) | |
| def print_tracks(ret): | |
| x_tracks, y_tracks = ret | |
| print('x = {}'.format(x_tracks)) | |
| print('y = {}'.format(y_tracks)) |