Skip to content

Commit

Permalink
Add performances measurement
Browse files Browse the repository at this point in the history
  • Loading branch information
pierre-jezegou committed May 20, 2024
1 parent afa7213 commit 3ee2754
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 17 deletions.
12 changes: 12 additions & 0 deletions additional_algorithms/template_2D_intersections.tex.jinja
Original file line number Diff line number Diff line change
@@ -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}
8 changes: 8 additions & 0 deletions additional_algorithms/template_performances.tex.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
\begin{tikzpicture}
\begin{axis}[]
{% for metric in metrics %}
{{ metric }};
{% endfor %}
\end{axis}
\end{tikzpicture}

105 changes: 105 additions & 0 deletions performances.py
Original file line number Diff line number Diff line change
@@ -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))
51 changes: 34 additions & 17 deletions sweep_lines.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down Expand Up @@ -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():
Expand All @@ -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
Expand All @@ -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:
Expand All @@ -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
Expand All @@ -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
50 changes: 50 additions & 0 deletions sweep_lines_to_pgf.py
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit 3ee2754

Please sign in to comment.