|  | """ Simple and correct version of Channel formation. | 
|  |  | 
|  | channel.py is actually broken for some track configuration, and generates | 
|  | an excessive number of dummy tracks to fill empty space (channel.Channel = >2M | 
|  | versus channel2.Channel ~70k). | 
|  |  | 
|  | """ | 
|  |  | 
|  |  | 
|  | class Channel(object): | 
|  | """ Packs tracks into ptc tracks | 
|  |  | 
|  | >>> tracks = [ | 
|  | ...     (1, 3, 0), | 
|  | ...     (1, 1, 1), | 
|  | ...     (4, 5, 2), | 
|  | ...     (4, 4, 3), | 
|  | ...     (0, 10, 4), | 
|  | ...     ] | 
|  | >>> channel_model = Channel(tracks) | 
|  | >>> channel_model.pack_tracks() | 
|  | >>> for ptc, tree in enumerate(channel_model.trees): | 
|  | ...     print('ptc={}'.format(ptc)) | 
|  | ...     for itr in tree: | 
|  | ...         x, y, idx = tracks[itr[2]] | 
|  | ...         assert idx == itr[2] | 
|  | ...         print(' tracks[{}] = ({}, {})'.format(itr[2], x, y)) | 
|  | ptc=0 | 
|  | tracks[4] = (0, 10) | 
|  | ptc=1 | 
|  | tracks[0] = (1, 3) | 
|  | tracks[2] = (4, 5) | 
|  | ptc=2 | 
|  | tracks[1] = (1, 1) | 
|  | tracks[3] = (4, 4) | 
|  | >>> for ptc, min_v, max_v in channel_model.fill_empty(0, 10): | 
|  | ...     print('ptc={} ({}, {})'.format(ptc, min_v, max_v)) | 
|  | ptc=1 (0, 0) | 
|  | ptc=1 (6, 10) | 
|  | ptc=2 (0, 0) | 
|  | ptc=2 (2, 3) | 
|  | ptc=2 (5, 10) | 
|  | """ | 
|  |  | 
|  | def __init__(self, tracks): | 
|  | """ | 
|  |  | 
|  | Attributes | 
|  | ---------- | 
|  | tracks : list of tuples of (min, max, idx) | 
|  | """ | 
|  | self.trees = [] | 
|  | self.tracks = sorted(tracks, key=lambda x: x[1] - x[0]) | 
|  |  | 
|  | def _start_track(self, track): | 
|  | self.trees.append([track]) | 
|  |  | 
|  | def _add_track_to_tree(self, track, idx=-1): | 
|  | self.trees[idx].append(track) | 
|  |  | 
|  | def _verify_trees(self): | 
|  | for tree in self.trees: | 
|  | for a, b in zip(tree, tree[1:]): | 
|  | assert a[1] <= b[0] | 
|  |  | 
|  | def pack_tracks(self): | 
|  | """pack all tracks | 
|  |  | 
|  | Algorithm: | 
|  |  | 
|  | 1. Sort tracks by length, shortest tracks first.  Popping from back | 
|  | of python lists is O(1). | 
|  | 2. Create stack for each starting values, inserting in length order. | 
|  | 3. Starting with the lowest starting value, greedly pack tracks | 
|  | Algorithm weaknesses: | 
|  | - Linear scan for lowest starting value | 
|  | - Linear scan for packing | 
|  |  | 
|  | Both weaknesses are O(Number grid dim * Number of tracks) in | 
|  | pathological case, however grid dimensions tend to be fairly small, | 
|  | (e.g. 50T is 150), so scans are practically fast. | 
|  |  | 
|  | If the grid dimension size grows large, revisit how to find the | 
|  | lowest starting value and next bucket pack.  Relevant operation is | 
|  | given coordinate, find next largest non-empty bucket. | 
|  | 3a. Pop largest track from smallest starting value, creating a new | 
|  | channel | 
|  | 3b. Pop largest track starting from end of previous track until no | 
|  | tracks can follow. | 
|  | 3c. Repeat 3 until everything is packed. | 
|  |  | 
|  | """ | 
|  |  | 
|  | by_low = {} | 
|  |  | 
|  | def pop(low): | 
|  | track = by_low[low].pop() | 
|  |  | 
|  | if len(by_low[low]) == 0: | 
|  | del by_low[low] | 
|  |  | 
|  | return track | 
|  |  | 
|  | for low, high, key in self.tracks: | 
|  | if low not in by_low: | 
|  | by_low[low] = [] | 
|  |  | 
|  | by_low[low].append((high, key)) | 
|  |  | 
|  | if len(by_low) > 0: | 
|  | high = max(by_low) | 
|  |  | 
|  | while len(by_low) > 0: | 
|  | track_low = min(by_low) | 
|  | track_high, key = pop(track_low) | 
|  |  | 
|  | self._start_track((track_low, track_high, key)) | 
|  |  | 
|  | while track_high is not None: | 
|  | start = track_high + 1 | 
|  | track_high = None | 
|  | for track_low in range(start, high + 1): | 
|  | if track_low in by_low: | 
|  | track_high, key = pop(track_low) | 
|  | self._add_track_to_tree((track_low, track_high, key)) | 
|  | break | 
|  |  | 
|  | self._verify_trees() | 
|  |  | 
|  | def fill_empty(self, min_value, max_value): | 
|  | """Generator that yields tracks for any gaps in the channels. | 
|  | """ | 
|  | for idx, tree in enumerate(self.trees): | 
|  | tracks = sorted(tree, key=lambda x: x[0]) | 
|  |  | 
|  | if min_value <= tracks[0][0] - 1: | 
|  | yield (idx, min_value, tracks[0][0] - 1) | 
|  |  | 
|  | for cur_track, next_track in zip(tracks, tracks[1:]): | 
|  | if cur_track[1] + 1 <= next_track[0] - 1: | 
|  | yield (idx, cur_track[1] + 1, next_track[0] - 1) | 
|  |  | 
|  | if tracks[-1][1] + 1 <= max_value: | 
|  | yield (idx, tracks[-1][1] + 1, max_value) |