diff --git a/additional_algorithms/template_2D_intersections.tex.jinja b/additional_algorithms/template_2D_intersections.tex.jinja new file mode 100644 index 0000000..fb3e88c --- /dev/null +++ b/additional_algorithms/template_2D_intersections.tex.jinja @@ -0,0 +1,12 @@ +\begin{tikzpicture} + \begin{axis} + % Segments + {% for segment in segments %} + {{ segment }}; + {% endfor %} + % Intersections + {% for intersection in intersections %} + \node[diamond,fill=blue, inner sep=2pt] at {{ intersection }} {}; + {% endfor %} + \end{axis} +\end{tikzpicture} \ No newline at end of file diff --git a/additional_algorithms/template_performances.tex.jinja b/additional_algorithms/template_performances.tex.jinja new file mode 100644 index 0000000..ed1a071 --- /dev/null +++ b/additional_algorithms/template_performances.tex.jinja @@ -0,0 +1,8 @@ +\begin{tikzpicture} +\begin{axis}[] +{% for metric in metrics %} + {{ metric }}; + {% endfor %} +\end{axis} +\end{tikzpicture} + diff --git a/performances.py b/performances.py new file mode 100644 index 0000000..26ea54b --- /dev/null +++ b/performances.py @@ -0,0 +1,105 @@ +import random +import time +from jinja2 import Template +from sweep_lines import Segment, Point, solve, naive_solve + +# sizes = [10, 20, 30 , 40, 50, 60, 70, 80, 90, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000] +sizes = [i for i in range(10, 501, 10)] +NUMBER_OF_TESTS = 10 + +TEMPLATE_NAME: str = 'additional_algorithms/template_performances.tex.jinja' + +class Test(): + """Class to store the results of a test.""" + def __init__(self, + size: int, + sweep_time: float, + naive_time: float, + sweep_intersections: int, + naive_intersections: int): + self.size = size + self.sweep_time = sweep_time + self.naive_time = naive_time + self.sweep_intersections = sweep_intersections + self.naive_intersections = naive_intersections + + +def generate_random_segments(size) -> list[Segment]: + """Generate a list of random segments.""" + segments = [] + for _ in range(size): + x1, x2 = None, None + while x1 == x2: + x1, y1 = random.randint(0, 10), random.randint(0, 10) + x2, y2 = random.randint(0, 10), random.randint(0, 10) + segment = Segment(Point(x1, y1), Point(x2, y2)) + segments.append(segment) + return segments + +def get_performances() -> list[dict]: + """Mesearure the performance of the sweep line algorithm.""" + performances = [] + + for size in sizes: + for _ in range(NUMBER_OF_TESTS): + segments = generate_random_segments(size) + start_time = time.time() + result = solve(segments) + end_time = time.time() + sweep_intersections = len(result) + sweep_time = end_time - start_time + + start_time = time.time() + result_naive = naive_solve(segments) + end_time = time.time() + naive_intersections = len(result_naive) + naive_time = end_time - start_time + + performances.append(Test(size=size, + sweep_time=sweep_time, + naive_time=naive_time, + sweep_intersections=sweep_intersections, + naive_intersections=naive_intersections)) + + return performances + +class TestSerie: + def __init__(self, title: str, metric: str, raw_data: list[Test], color: str): + self.title = title + self.metric = metric + self.color = color + self.data = [(test.size, getattr(test, metric)) for test in raw_data] + + def data_to_str(self): + return "".join([f"({size}, {time})\n" for size, time in self.data]) + + def plot_pgf(self): + return f"""\\addplot[only marks, mark=x, color={self.color}] coordinates {{ + {self.data_to_str()} + }}; + \\addlegendentry{{{self.title}}};""" + + +def plot_pgf(performances: list[Test]) -> str: + """Plot the performances in a PGFPlots plot.""" + series = [ + # TestSerie("Naive CPU Time", "naive_time", performances, "red"), + # TestSerie("Sweep CPU Time", "sweep_time", performances, "blue"), + TestSerie("Naive intersections", "naive_intersections", performances, "red"), + TestSerie("Sweep intersections", "sweep_intersections", performances, "blue"), + ] + + with open(TEMPLATE_NAME, 'r') as file: + template_content = file.read() + + template = Template(template_content) + + rendered_template = template.render(metrics=[serie.plot_pgf() for serie in series]) + + return rendered_template + + + +if __name__ == "__main__": + result = get_performances() + print(plot_pgf(result)) \ No newline at end of file diff --git a/sweep_lines.py b/sweep_lines.py index 8a7f643..dcdec29 100644 --- a/sweep_lines.py +++ b/sweep_lines.py @@ -20,6 +20,14 @@ def __repr__(self): """Return a string representation of the point.""" return f"({self.x}, {self.y})" + def __hash__(self) -> int: + """Return the hash value of the point.""" + return hash((self.x, self.y)) + + def format_point(self) -> str: + """Return the point in the format (x, y).""" + return f"(axis cs:{self.x}, {self.y})" + class Segment(): """A line segment in the plane.""" @@ -69,6 +77,9 @@ def intersection(self, other: 'Segment') -> Point | bool: return Point(intersection_x, intersection_y) + def segment_to_pgf(self) -> str: + """Return the segment in the PGFPlots format.""" + return f"\\addplot[red, mark=*] coordinates {{{self.start} {self.end}}};" class Event(): @@ -82,7 +93,11 @@ def __repr__(self): return f"(segment={self.segment_id}, type={self.event_type}, point.x {self.x})" def solve(segments: list[Segment]) -> list[Point]: - """Find the intersection points of a set of line segments.""" + """ + Find the intersection points of a set of line segments. + Use the sweep line algorithm to find the intersections. + Complexity: O(n log n) where n is the number of segments. + """ events = [] # Add all the start and end points of the segments to the events list @@ -97,7 +112,7 @@ def solve(segments: list[Segment]) -> list[Point]: # Initialize the sweep line algorithm status: list[Segment] = [] # list of segments that are currently intersecting the sweep line - intersections: list[Point] = [] # list of intersection points + intersections: set[Point] = set() # list of intersection points # Process the events one by one from left to right for event in events: @@ -114,12 +129,12 @@ def solve(segments: list[Segment]) -> list[Point]: # Compare with below segment inter = status[idx].intersection(status[idx - 1]) if inter: - intersections.append(inter) + intersections.add(inter) if idx < len(status) - 1: # Compare with above segment inter = status[idx].intersection(status[idx + 1]) if inter: - intersections.append(inter) + intersections.add(inter) else: # get index of segment in status @@ -129,19 +144,21 @@ def solve(segments: list[Segment]) -> list[Point]: # Check for intersection between the neighbors inter = status[idx - 1].intersection(status[idx + 1]) if inter: - intersections.append(inter) + intersections.add(inter) status.remove(segment) return intersections - -if __name__ == "__main__": - # Example - segment1 = Segment(Point(1, 1), Point(4, 4)) - segment2 = Segment(Point(1, 3), Point(3, 1)) - segment3 = Segment(Point(3, 1), Point(5, 3)) - segment4 = Segment(Point(3, 2), Point(5, 0)) - - segments = [segment1, segment2, segment3, segment4] - intersections = solve(segments) - for intersection in intersections: - print(intersection) +def naive_solve(segments: list[Segment]) -> list[Point]: + """ + Naive solution to find the intersection points of a set of line segments. + Compare all pairs of segments to find intersections. + Complexity: O(n^2) where n is the number of segments. + """ + intersections = set() + for i, segment1 in enumerate(segments): + for j, segment2 in enumerate(segments): + if i != j: + inter = segment1.intersection(segment2) + if inter: + intersections.add(inter) + return intersections diff --git a/sweep_lines_to_pgf.py b/sweep_lines_to_pgf.py new file mode 100644 index 0000000..e46cf36 --- /dev/null +++ b/sweep_lines_to_pgf.py @@ -0,0 +1,50 @@ +from jinja2 import Template +from sweep_lines import Segment, Point, solve + +TEMPLATE_NAME: str = 'additional_algorithms/template_2D_intersections.tex.jinja' + +def segments_to_pgf(segments: list[Segment], display_intersections: bool = True) -> None: + """Convert a list of segments to a PGFPlots plot.""" + intersections = solve(segments) if display_intersections else [] + + # Load the Jinja template from a file + with open(TEMPLATE_NAME, 'r') as file: + template_content = file.read() + + template = Template(template_content) + + segments_str = [segment.segment_to_pgf() for segment in segments] + + if intersections is not None: + intersections_str = [intersection.format_point() for intersection in intersections] + else: + intersections_str = '' + + rendered_template = template.render(segments=segments_str, intersections=intersections_str) + + return rendered_template + + +if __name__ == "__main__": + # Example + # segment1 = Segment(Point(1, 1), Point(4, 4)) + # segment2 = Segment(Point(1, 3), Point(3, 1)) + # segment3 = Segment(Point(3, 1), Point(5, 3)) + # segment4 = Segment(Point(3, 2), Point(5, 0)) + + # segments = [segment1, segment2, segment3, segment4] + + # Genetate 100 random segments + import random + segments = [] + for _ in range(10): + x1, x2 = None, None + while x1 == x2: + x1, y1 = random.randint(0, 10), random.randint(0, 10) + x2, y2 = random.randint(0, 10), random.randint(0, 10) + segment = Segment(Point(x1, y1), Point(x2, y2)) + segments.append(segment) + + pgf = segments_to_pgf(segments) + + print(pgf)