blob: d461d48195cd559c4f66fc55a7b8a6ecffa30835 [file] [log] [blame] [edit]
#!/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))