Beispiel #1
0
class EventsQueue:
    __slots__ = 'context', 'key', '_queue'

    def __init__(self, context: Context) -> None:
        self.context = context
        key = self.key = partial(EventsQueueKey, context.angle_orientation)
        self._queue = PriorityQueue(key=key)

    __repr__ = generate_repr(__init__)

    def __bool__(self) -> bool:
        return bool(self._queue)

    def peek(self) -> Event:
        return self._queue.peek()

    @abstractmethod
    def register(self, segments_endpoints: Iterable[SegmentEndpoints], *,
                 from_test: bool) -> None:
        """
        Registers segments in the events queue.
        """

    @abstractmethod
    def sweep(self, stop_x: Scalar) -> Iterable[LeftEvent]:
        """
        Sweeps plane and emits processed segments' events.
        """

    def _divide_segment(self, event: LeftEvent, break_point: Point) -> None:
        self._queue.push(event.divide(break_point))
        self._queue.push(event.right)
Beispiel #2
0
class Builder:
    __slots__ = ('index', '_beach_line', '_circle_events', '_end_points',
                 'site_events', '_site_event_index')

    def __init__(self,
                 index: int = 0,
                 site_events: Optional[List[SiteEvent]] = None) -> None:
        self.index = index
        self._beach_line = red_black.Tree.from_components([])
        self._circle_events = PriorityQueue(key=itemgetter(0))
        self._end_points = PriorityQueue(key=itemgetter(0))
        self.site_events = [] if site_events is None else site_events
        self._site_event_index = None

    __repr__ = generate_repr(__init__)

    @property
    def beach_line(self) -> List[Tuple[BeachLineKey, BeachLineValue]]:
        return [node.item for node in self._beach_line]

    @property
    def site_event(self) -> SiteEvent:
        return self.site_events[self._site_event_index]

    @property
    def site_event_index(self) -> int:
        return (len(self.site_events)
                if self._site_event_index is None else self._site_event_index)

    def activate_circle_event(self, first_site: SiteEvent,
                              second_site: SiteEvent, third_site: SiteEvent,
                              bisector_node: red_black.Node) -> None:
        event = CircleEvent(0., 0., 0.)
        # check if the three input sites create a circle event
        if compute_circle_event(event, first_site, second_site, third_site):
            # add the new circle event to the circle events queue;
            # update bisector's circle event iterator to point
            # to the new circle event in the circle event queue
            self._circle_events.push((event, bisector_node))
            bisector_node.value.circle_event = event

    def construct(self, output: 'Diagram') -> None:
        self.init_sites_queue()
        self.init_beach_line(output)
        while (self._circle_events
               or self._site_event_index < len(self.site_events)):
            if (not self._circle_events
                    or (self._site_event_index < len(self.site_events)
                        and self.site_event < self._circle_events.peek()[0])):
                self.process_site_event(output)
            else:
                self.process_circle_event(output)
            while (self._circle_events
                   and not self._circle_events.peek()[0].is_active):
                self._circle_events.pop()
        self._beach_line.clear()
        output._build()

    @staticmethod
    def deactivate_circle_event(value: BeachLineValue) -> None:
        if value.circle_event is not None:
            value.circle_event.deactivate()
            value.circle_event = None

    def init_beach_line(self, diagram: 'Diagram') -> None:
        if not self.site_events:
            return
        elif len(self.site_events) == 1:
            # handle single site event case
            diagram._process_single_site(self.site_events[0])
            self._site_event_index += 1
        else:
            skip = 0
            while (self._site_event_index < len(self.site_events)
                   and are_vertical_endpoints(self.site_event.start,
                                              self.site_events[0].start)
                   and self.site_event.is_vertical):
                self._site_event_index += 1
                skip += 1
            if skip == 1:
                # init beach line with the first two sites
                self.init_beach_line_default(diagram)
            else:
                # init beach line with collinear vertical sites
                self.init_beach_line_collinear_sites(diagram)

    def init_beach_line_collinear_sites(self, diagram: 'Diagram') -> None:
        first_index, second_index = 0, 1
        while second_index != self._site_event_index:
            # create a new beach line node
            first_site, second_site = (self.site_events[first_index],
                                       self.site_events[second_index])
            new_node = BeachLineKey(first_site, second_site)
            # update the diagram
            edge, _ = diagram._insert_new_edge(first_site, second_site)
            # insert a new bisector into the beach line
            self._beach_line.insert(new_node, BeachLineValue(edge))
            first_index += 1
            second_index += 1

    def init_beach_line_default(self, diagram: 'Diagram') -> None:
        # get the first and the second site event
        self.insert_new_arc(self.site_events[0], self.site_events[0],
                            self.site_events[1], diagram)
        # the second site was already processed, move the position
        self._site_event_index += 1

    def insert_new_arc(self, first_arc_site: SiteEvent,
                       second_arc_site: SiteEvent, site_event: SiteEvent,
                       output: 'Diagram') -> red_black.Node:
        # create two new bisectors with opposite directions
        new_left_node = BeachLineKey(first_arc_site, site_event)
        new_right_node = BeachLineKey(site_event, second_arc_site)
        # set correct orientation for the first site of the second node
        if site_event.is_segment:
            new_right_node.left_site.inverse()
        # update the output
        edges = output._insert_new_edge(second_arc_site, site_event)
        self._beach_line.insert(new_right_node, BeachLineValue(edges[1]))
        if site_event.is_segment:
            # update the beach line with temporary bisector,
            # that will # disappear after processing site event
            # corresponding to the second endpoint of the segment site
            new_node = BeachLineKey(site_event, site_event)
            new_node.right_site.inverse()
            node = self._beach_line.insert(new_node, BeachLineValue(None))
            # update the data structure that holds temporary bisectors
            self._end_points.push((site_event.end, node))
        return self._beach_line.insert(new_left_node, BeachLineValue(edges[0]))

    def init_sites_queue(self) -> None:
        self.site_events.sort()
        self.site_events = to_unique_just_seen(self.site_events)
        for index, event in enumerate(self.site_events):
            event.sorted_index = index
        self._site_event_index = 0

    def insert_point(self, point: Point) -> int:
        index = self.index
        self.site_events.append(
            SiteEvent.from_point(point,
                                 initial_index=index,
                                 source_category=SourceCategory.SINGLE_POINT))
        self.index += 1
        return index

    def insert_segment(self, segment: Segment) -> int:
        site_events = self.site_events
        index = self.index
        start, end = segment.start, segment.end
        site_events.append(
            SiteEvent.from_point(
                start,
                initial_index=index,
                source_category=SourceCategory.SEGMENT_START_POINT))
        site_events.append(
            SiteEvent.from_point(
                end,
                initial_index=index,
                source_category=SourceCategory.SEGMENT_END_POINT))
        site_events.append(
            SiteEvent(start,
                      end,
                      source_category=SourceCategory.INITIAL_SEGMENT,
                      initial_index=index) if start < end else SiteEvent(
                          end,
                          start,
                          source_category=SourceCategory.REVERSE_SEGMENT,
                          initial_index=index))
        self.index += 1
        return index

    def process_circle_event(self, output: 'Diagram') -> None:
        circle_event, first_node = self._circle_events.pop()
        last_node = first_node
        second_site = copy(first_node.key.right_site)
        second_bisector = first_node.value.edge
        first_node = self._beach_line.predecessor(first_node)
        first_bisector = first_node.value.edge
        first_site = copy(first_node.key.left_site)
        if (not first_site.is_segment and second_site.is_segment
                and second_site.end == first_site.start):
            second_site.inverse()
        first_node.key.right_site = second_site
        first_node.value.edge, _ = output._insert_new_edge_from_intersection(
            first_site, second_site, circle_event, first_bisector,
            second_bisector)
        self._beach_line.remove(last_node)
        last_node = first_node
        if first_node is not self._beach_line.min():
            self.deactivate_circle_event(first_node.value)
            first_node = self._beach_line.predecessor(first_node)
            self.activate_circle_event(first_node.key.left_site, first_site,
                                       second_site, last_node)
        last_node = self._beach_line.successor(last_node)
        if last_node is not red_black.NIL:
            self.deactivate_circle_event(last_node.value)
            self.activate_circle_event(first_site, second_site,
                                       last_node.key.right_site, last_node)

    def process_site_event(self, output: 'Diagram') -> None:
        last_index = self._site_event_index + 1
        if not self.site_event.is_segment:
            while (self._end_points
                   and self._end_points.peek()[0] == self.site_event.start):
                _, node = self._end_points.pop()
                self._beach_line.remove(node)
        else:
            while (last_index < len(self.site_events)
                   and self.site_events[last_index].is_segment
                   and (self.site_events[last_index].start
                        == self.site_event.start)):
                last_index += 1
        # find the node in the binary search tree
        # with left arc lying above the new site point
        new_key = BeachLineKey(self.site_event, self.site_event)
        right_node = self._beach_line.supremum(new_key)
        while self._site_event_index < last_index:
            site_event = copy(self.site_event)
            left_node = right_node
            if right_node is red_black.NIL:
                # the above arc corresponds to the second arc of the last node,
                # move the iterator to the last node
                left_node = self._beach_line.max()
                # get the second site of the last node
                arc_site = left_node.key.right_site
                # insert new nodes into the beach line, update the output
                right_node = self.insert_new_arc(arc_site, arc_site,
                                                 site_event, output)
                # add a candidate circle to the circle event queue;
                # there could be only one new circle event
                # formed by a new bisector and the one on the left
                self.activate_circle_event(left_node.key.left_site,
                                           left_node.key.right_site,
                                           site_event, right_node)
            elif right_node is self._beach_line.min():
                # the above arc corresponds to the first site of the first node
                arc_site = right_node.key.left_site
                # Insert new nodes into the beach line. Update the output.
                left_node = self.insert_new_arc(arc_site, arc_site, site_event,
                                                output)
                # if the site event is a segment, update its direction
                if site_event.is_segment:
                    site_event.inverse()
                # add a candidate circle to the circle event queue;
                # there could be only one new circle event
                # formed by a new bisector and the one on the right
                self.activate_circle_event(site_event,
                                           right_node.key.left_site,
                                           right_node.key.right_site,
                                           right_node)
                right_node = left_node
            else:
                # the above arc corresponds neither to the first,
                # nor to the last site in the beach line
                second_arc_site = right_node.key.left_site
                third_site = right_node.key.right_site
                # remove the candidate circle from the event queue
                self.deactivate_circle_event(right_node.value)
                left_node = self._beach_line.predecessor(left_node)
                first_arc_site = left_node.key.right_site
                first_site = left_node.key.left_site
                # insert new nodes into the beach line. Update the output
                new_node = self.insert_new_arc(first_arc_site, second_arc_site,
                                               site_event, output)
                # add candidate circles to the circle event queue;
                # there could be up to two circle events
                # formed by a new bisector and the one on the left or right
                self.activate_circle_event(first_site, first_arc_site,
                                           site_event, new_node)
                # if the site event is a segment, update its direction
                if site_event.is_segment:
                    site_event.inverse()
                self.activate_circle_event(site_event, second_arc_site,
                                           third_site, right_node)
                right_node = new_node
            self._site_event_index += 1
Beispiel #3
0
def test_base_case(priority_queue: PriorityQueue) -> None:
    with pytest.raises(ValueError):
        priority_queue.peek()
Beispiel #4
0
def test_step(priority_queue: PriorityQueue) -> None:
    result = priority_queue.peek()

    assert result in priority_queue.values()
    assert all(not item < priority_queue._item_factory(result)
               for item in priority_queue._items)
class EventsQueue:
    __slots__ = 'context', '_queue'

    def __init__(self, context: Context) -> None:
        self.context = context
        self._queue = PriorityQueue(key=EventsQueueKey)

    __repr__ = generate_repr(__init__)

    def __bool__(self) -> bool:
        return bool(self._queue)

    def detect_intersection(self, below_event: Event, event: Event) -> None:
        relation = self.context.segments_relation(below_event.start,
                                                  below_event.end, event.start,
                                                  event.end)
        if relation is Relation.TOUCH or relation is Relation.CROSS:
            # segments touch or cross
            point = self.context.segments_intersection(below_event.start,
                                                       below_event.end,
                                                       event.start, event.end)
            if point != below_event.start and point != below_event.end:
                self._divide_segment(below_event, point)
            if point != event.start and point != event.end:
                self._divide_segment(event, point)
            event.set_both_relations(max(event.relation, relation))
            below_event.set_both_relations(max(below_event.relation, relation))
        elif relation is not Relation.DISJOINT:
            # segments overlap
            starts_equal = event.start == below_event.start
            start_min, start_max = ((None, None) if starts_equal else (
                (event, below_event)
                if EventsQueueKey(event) < EventsQueueKey(below_event) else
                (below_event, event)))
            ends_equal = event.end == below_event.end
            end_min, end_max = ((None, None) if ends_equal else
                                ((event.complement, below_event.complement) if
                                 (EventsQueueKey(event.complement) <
                                  EventsQueueKey(below_event.complement)) else
                                 (below_event.complement, event.complement)))
            if starts_equal:
                if ends_equal:
                    # segments are equal
                    event.set_both_relations(relation)
                    below_event.set_both_relations(relation)
                else:
                    # segments share the left endpoint
                    end_min.set_both_relations(relation)
                    end_max.complement.relation = relation
                    self._divide_segment(end_max.complement, end_min.start)
            elif ends_equal:
                # segments share the right endpoint
                start_max.set_both_relations(relation)
                start_min.complement.relation = relation
                self._divide_segment(start_min, start_max.start)
            elif start_min is end_max.complement:
                # one line segment includes the other one
                start_max.set_both_relations(relation)
                start_min_original_relationship = start_min.relation
                start_min.relation = relation
                self._divide_segment(start_min, end_min.start)
                start_min.relation = start_min_original_relationship
                start_min.complement.relation = relation
                self._divide_segment(start_min, start_max.start)
            else:
                # no line segment includes the other one
                start_max.relation = relation
                self._divide_segment(start_max, end_min.start)
                start_min.complement.relation = relation
                self._divide_segment(start_min, start_max.start)

    def peek(self) -> Event:
        return self._queue.peek()

    def pop(self) -> Event:
        return self._queue.pop()

    def push(self, event: Event) -> None:
        if event.start == event.end:
            raise ValueError('Degenerate segment found '
                             'with both endpoints being: {}.'.format(
                                 event.start))
        self._queue.push(event)

    def _divide_segment(self, event: Event, break_point: Point) -> None:
        left_event = event.complement.complement = Event(
            start=break_point,
            complement=event.complement,
            is_left_endpoint=True,
            relation=event.complement.relation,
            segments_ids=event.segments_ids)
        right_event = event.complement = Event(
            start=break_point,
            complement=event,
            is_left_endpoint=False,
            relation=event.relation,
            segments_ids=event.complement.segments_ids)
        self.push(left_event)
        self.push(right_event)
Beispiel #6
0
class EventsQueue:
    @classmethod
    def from_segments(cls, segments: Sequence[Segment], *,
                      context: Context) -> 'EventsQueue':
        result = cls(context)
        for index, segment in enumerate(segments):
            event = LeftEvent.from_segment(segment, index)
            result.push(event)
            result.push(event.right)
        return result

    __slots__ = 'context', '_queue'

    def __init__(self, context: Context) -> None:
        self.context = context
        self._queue = PriorityQueue(key=EventsQueueKey)

    __repr__ = generate_repr(__init__)

    def __bool__(self) -> bool:
        return bool(self._queue)

    def detect_intersection(self, below_event: LeftEvent, event: LeftEvent,
                            sweep_line: SweepLine) -> None:
        relation = self.context.segments_relation(below_event, event)
        if relation is Relation.DISJOINT:
            return
        elif relation is Relation.TOUCH or relation is Relation.CROSS:
            # segments touch or cross
            point = self.context.segments_intersection(below_event, event)
            assert event.segments_ids.isdisjoint(below_event.segments_ids)
            if point != below_event.start and point != below_event.end:
                below_below = sweep_line.below(below_event)
                assert not (below_below is not None and below_below.start
                            == below_event.start and below_below.end == point)
                self.push(below_event.divide(point))
                self.push(below_event.right)
            if point != event.start and point != event.end:
                above_event = sweep_line.above(event)
                if (above_event is not None
                        and above_event.start == event.start
                        and above_event.end == point):
                    sweep_line.remove(above_event)
                    self.push(event.divide(point))
                    self.push(event.right)
                    event.merge_with(above_event)
                else:
                    self.push(event.divide(point))
                    self.push(event.right)
        else:
            # segments overlap
            starts_equal = event.start == below_event.start
            start_min, start_max = ((event, below_event) if (
                starts_equal
                or EventsQueueKey(event) < EventsQueueKey(below_event)) else
                                    (below_event, event))
            ends_equal = event.end == below_event.end
            end_min, end_max = ((event.right,
                                 below_event.right) if ends_equal or
                                (EventsQueueKey(event.right) < EventsQueueKey(
                                    below_event.right)) else
                                (below_event.right, event.right))
            if starts_equal:
                assert not ends_equal
                # segments share the left endpoint
                sweep_line.remove(end_max.left)
                self.push(end_max.left.divide(end_min.start))
                event.merge_with(below_event)
            elif ends_equal:
                # segments share the right endpoint
                start_max.merge_with(start_min.divide(start_max.start))
                self.push(start_min.right)
            elif start_min is end_max.left:
                # one line segment includes the other one
                self.push(start_min.divide(end_min.start))
                self.push(start_min.right)
                start_max.merge_with(start_min.divide(start_max.start))
                self.push(start_min.right)
            else:
                # no line segment includes the other one
                self.push(start_max.divide(end_min.start))
                start_max.merge_with(start_min.divide(start_max.start))
                self.push(start_max.right)
                self.push(start_min.right)

    def peek(self) -> Event:
        return self._queue.peek()

    def pop(self) -> Event:
        return self._queue.pop()

    def push(self, event: Event) -> None:
        if event.start == event.end:
            raise ValueError('Degenerate segment found '
                             'with both endpoints being: {}.'.format(
                                 event.start))
        self._queue.push(event)