|  | #!/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)) |