Skip to content

Conversation

@codeflash-ai
Copy link

@codeflash-ai codeflash-ai bot commented Dec 9, 2025

📄 15% (0.15x) speedup for _calculate_quad_point_coordinates in lib/matplotlib/backends/backend_pdf.py

⏱️ Runtime : 2.74 milliseconds 2.38 milliseconds (best of 67 runs)

📝 Explanation and details

The optimization achieves a 15% speedup through two key improvements:

1. Zero-angle fast path: When angle=0 (no rotation), the code bypasses expensive trigonometric calculations by directly setting sin_angle=0.0 and cos_angle=1.0. This eliminates calls to math.radians(), math.sin(), and math.cos() for the common case of non-rotated rectangles.

2. Computation reuse: Pre-computes height_sin, height_cos, width_cos, and width_sin once, eliminating repeated multiplications in the coordinate calculations.

Performance impact by use case:

  • Zero-angle cases (test results show 64-85% faster): Massive speedup when rectangles aren't rotated, which appears common in PDF rendering
  • Non-zero angles (test results show 2-8% slower): Slight overhead from the conditional check and extra variable assignments, but minimal impact
  • Mixed workloads (test results show 4-8% faster overall): The optimization pays off when zero-angle cases are frequent

Context relevance: The function is called from _get_coordinates_of_block() in PDF backend rendering, suggesting it's in a hot path for PDF generation. Since many PDF elements (text, images, shapes) are typically unrotated, the zero-angle fast path should provide significant real-world benefits for matplotlib's PDF output performance.

The optimization preserves exact mathematical behavior while trading a small conditional overhead for substantial gains in the common unrotated case.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 3999 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
🌀 Generated Regression Tests and Runtime
import math

# imports
from matplotlib.backends.backend_pdf import _calculate_quad_point_coordinates


# Helper function for comparing tuples with float values
def tuples_close(t1, t2, rel_tol=1e-9, abs_tol=0.0):
    return all(
        math.isclose(a, b, rel_tol=rel_tol, abs_tol=abs_tol) for a, b in zip(t1, t2)
    )


def quads_close(q1, q2, rel_tol=1e-9, abs_tol=0.0):
    return all(tuples_close(a, b, rel_tol, abs_tol) for a, b in zip(q1, q2))


# Basic Test Cases


def test_no_rotation_origin_unit_square():
    # Rectangle at origin, width=1, height=1, angle=0
    codeflash_output = _calculate_quad_point_coordinates(0, 0, 1, 1, 0)
    result = codeflash_output  # 3.36μs -> 1.81μs (85.1% faster)
    expected = ((0, 0), (1, 0), (1, 1), (0, 1))


def test_no_rotation_arbitrary_position():
    # Rectangle at (2,3), width=4, height=5, angle=0
    codeflash_output = _calculate_quad_point_coordinates(2, 3, 4, 5, 0)
    result = codeflash_output  # 3.20μs -> 1.83μs (74.8% faster)
    expected = ((2, 3), (6, 3), (6, 8), (2, 8))


def test_90_degree_rotation():
    # Rectangle at origin, width=2, height=3, angle=90
    codeflash_output = _calculate_quad_point_coordinates(0, 0, 2, 3, 90)
    result = codeflash_output  # 3.37μs -> 3.62μs (6.96% slower)
    # After 90 degree CCW rotation, width is along -y, height is along +x
    expected = ((0, 0), (0, -2), (3, -2), (3, 0))


def test_180_degree_rotation():
    # Rectangle at (1,1), width=2, height=3, angle=180
    codeflash_output = _calculate_quad_point_coordinates(1, 1, 2, 3, 180)
    result = codeflash_output  # 3.31μs -> 3.58μs (7.67% slower)
    # After 180 degree CCW rotation, width and height are reversed
    expected = ((1, 1), (-1, 1), (-1, -2), (1, -2))


def test_45_degree_rotation():
    # Rectangle at (0,0), width=2, height=2, angle=45
    codeflash_output = _calculate_quad_point_coordinates(0, 0, 2, 2, 45)
    result = codeflash_output  # 3.21μs -> 3.49μs (7.99% slower)
    sqrt2 = math.sqrt(2)
    expected = ((0, 0), (sqrt2, -sqrt2), (2 * sqrt2, 0), (sqrt2, sqrt2))


# Edge Test Cases


def test_zero_width():
    # Rectangle with zero width
    codeflash_output = _calculate_quad_point_coordinates(0, 0, 0, 2, 0)
    result = codeflash_output  # 3.38μs -> 1.94μs (74.8% faster)
    expected = ((0, 0), (0, 0), (0, 2), (0, 2))


def test_zero_height():
    # Rectangle with zero height
    codeflash_output = _calculate_quad_point_coordinates(0, 0, 2, 0, 0)
    result = codeflash_output  # 3.37μs -> 1.95μs (72.7% faster)
    expected = ((0, 0), (2, 0), (2, 0), (0, 0))


def test_negative_width():
    # Negative width, should work as per formula
    codeflash_output = _calculate_quad_point_coordinates(0, 0, -2, 2, 0)
    result = codeflash_output  # 3.42μs -> 1.95μs (75.2% faster)
    expected = ((0, 0), (-2, 0), (-2, 2), (0, 2))


def test_negative_height():
    # Negative height, should work as per formula
    codeflash_output = _calculate_quad_point_coordinates(0, 0, 2, -2, 0)
    result = codeflash_output  # 3.41μs -> 1.94μs (75.7% faster)
    expected = ((0, 0), (2, 0), (2, -2), (0, -2))


def test_large_angle_rotation():
    # Angle greater than 360 (e.g., 450 == 90 degrees)
    codeflash_output = _calculate_quad_point_coordinates(0, 0, 2, 2, 450)
    result = codeflash_output  # 3.70μs -> 3.80μs (2.81% slower)
    sqrt2 = math.sqrt(2)
    expected = ((0, 0), (0, -2), (2, -2), (2, 0))


def test_negative_angle():
    # Negative angle should rotate in the opposite direction
    codeflash_output = _calculate_quad_point_coordinates(0, 0, 2, 2, -90)
    result = codeflash_output  # 3.58μs -> 3.51μs (2.00% faster)
    expected = ((0, 0), (0, 2), (-2, 2), (-2, 0))


def test_float_inputs():
    # Inputs as floats
    codeflash_output = _calculate_quad_point_coordinates(1.5, -2.5, 3.2, 4.8, 30)
    result = codeflash_output  # 2.97μs -> 3.04μs (2.43% slower)
    angle = math.radians(-30)
    sin_angle = math.sin(angle)
    cos_angle = math.cos(angle)
    a = 1.5 + 4.8 * sin_angle
    b = -2.5 + 4.8 * cos_angle
    c = 1.5 + 3.2 * cos_angle + 4.8 * sin_angle
    d = -2.5 - 3.2 * sin_angle + 4.8 * cos_angle
    e = 1.5 + 3.2 * cos_angle
    f = -2.5 - 3.2 * sin_angle
    expected = ((1.5, -2.5), (e, f), (c, d), (a, b))


def test_zero_angle():
    # Angle is exactly zero
    codeflash_output = _calculate_quad_point_coordinates(5, 5, 10, 10, 0)
    result = codeflash_output  # 3.33μs -> 2.03μs (64.4% faster)
    expected = ((5, 5), (15, 5), (15, 15), (5, 15))


def test_full_rotation():
    # Angle is 360, should return same as angle=0
    codeflash_output = _calculate_quad_point_coordinates(2, 2, 3, 4, 0)
    result_0 = codeflash_output  # 3.44μs -> 2.05μs (67.9% faster)
    codeflash_output = _calculate_quad_point_coordinates(2, 2, 3, 4, 360)
    result_360 = codeflash_output  # 1.49μs -> 2.62μs (43.0% slower)


# Large Scale Test Cases


def test_many_rectangles_varied_angles():
    # Test 100 rectangles with increasing positions, sizes, and angles
    for i in range(100):
        x = i
        y = i * 2
        width = i + 1
        height = i + 2
        angle = (i * 3) % 360
        codeflash_output = _calculate_quad_point_coordinates(x, y, width, height, angle)
        result = codeflash_output  # 74.4μs -> 69.4μs (7.18% faster)
        for pt in result:
            pass


def test_large_values():
    # Test with very large values
    big = 1e8
    codeflash_output = _calculate_quad_point_coordinates(big, big, big, big, 45)
    result = codeflash_output  # 2.90μs -> 2.96μs (2.13% slower)
    # Should not raise or overflow
    for pt in result:
        pass


def test_many_rotations_performance():
    # Test 1000 rectangles with random angles, check for performance and correctness of shape
    for i in range(1000):
        x = 0
        y = 0
        width = 1 + i % 10
        height = 1 + (i * 3) % 10
        angle = (i * 7) % 360
        codeflash_output = _calculate_quad_point_coordinates(x, y, width, height, angle)
        quad = codeflash_output  # 690μs -> 640μs (7.68% faster)
        # The four points must be unique for nonzero width/height
        unique_points = set(quad)


def test_extreme_small_values():
    # Test with very small values
    small = 1e-10
    codeflash_output = _calculate_quad_point_coordinates(small, small, small, small, 30)
    result = codeflash_output  # 3.12μs -> 3.23μs (3.19% slower)
    # Should not collapse to a single point
    unique_points = set(result)


def test_rectangles_all_quadrants():
    # Test rectangles placed in all four quadrants
    for x, y in [(-10, -10), (10, -10), (10, 10), (-10, 10)]:
        codeflash_output = _calculate_quad_point_coordinates(x, y, 5, 7, 60)
        result = codeflash_output  # 6.66μs -> 6.49μs (2.59% faster)
        # All points should be in expected quadrant
        for pt in result:
            pass


# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
import math

# imports
from matplotlib.backends.backend_pdf import _calculate_quad_point_coordinates


# Helper function for comparing tuples of floats with tolerance
def tuples_close(t1, t2, tol=1e-9):
    for a, b in zip(t1, t2):
        if isinstance(a, tuple) and isinstance(b, tuple):
            tuples_close(a, b, tol)
        else:
            pass


# Basic Test Cases


def test_no_rotation_origin():
    # Rectangle at origin, no rotation
    codeflash_output = _calculate_quad_point_coordinates(0, 0, 2, 3, 0)
    result = codeflash_output  # 3.27μs -> 1.86μs (75.7% faster)
    expected = ((0, 0), (2, 0), (2, 3), (0, 3))
    tuples_close(result, expected)


def test_no_rotation_non_origin():
    # Rectangle at (1,1), no rotation
    codeflash_output = _calculate_quad_point_coordinates(1, 1, 4, 5, 0)
    result = codeflash_output  # 3.43μs -> 2.00μs (72.0% faster)
    expected = ((1, 1), (5, 1), (5, 6), (1, 6))
    tuples_close(result, expected)


def test_90_deg_rotation():
    # Rectangle at origin, 90 degree rotation
    codeflash_output = _calculate_quad_point_coordinates(0, 0, 2, 3, 90)
    result = codeflash_output  # 3.60μs -> 3.71μs (2.97% slower)
    # The rectangle should be rotated counterclockwise 90 degrees
    # Corner 1: (0,0)
    # Corner 2: (-0,2)
    # Corner 3: (-3,2)
    # Corner 4: (-3,0)
    expected = ((0, 0), (0, 2), (-3, 2), (-3, 0))
    tuples_close(result, expected)


def test_180_deg_rotation():
    # Rectangle at origin, 180 degree rotation
    codeflash_output = _calculate_quad_point_coordinates(0, 0, 2, 3, 180)
    result = codeflash_output  # 3.53μs -> 3.74μs (5.56% slower)
    expected = ((0, 0), (-2, 0), (-2, -3), (0, -3))
    tuples_close(result, expected)


def test_45_deg_rotation():
    # Rectangle at origin, 45 degree rotation
    codeflash_output = _calculate_quad_point_coordinates(0, 0, 1, 1, 45)
    result = codeflash_output  # 3.32μs -> 3.51μs (5.42% slower)
    # Calculate manually
    angle = math.radians(-45)
    sin_angle = math.sin(angle)
    cos_angle = math.cos(angle)
    a = 0 + 1 * sin_angle
    b = 0 + 1 * cos_angle
    c = 0 + 1 * cos_angle + 1 * sin_angle
    d = 0 - 1 * sin_angle + 1 * cos_angle
    e = 0 + 1 * cos_angle
    f = 0 - 1 * sin_angle
    expected = ((0, 0), (e, f), (c, d), (a, b))
    tuples_close(result, expected)


def test_negative_rotation():
    # Rectangle at origin, -90 degree rotation
    codeflash_output = _calculate_quad_point_coordinates(0, 0, 2, 3, -90)
    result = codeflash_output  # 3.45μs -> 3.50μs (1.29% slower)
    expected = ((0, 0), (0, -2), (3, -2), (3, 0))
    tuples_close(result, expected)


def test_float_inputs():
    # Rectangle at (1.5, 2.5), width=2.5, height=3.5, angle=30.5
    codeflash_output = _calculate_quad_point_coordinates(1.5, 2.5, 2.5, 3.5, 30.5)
    result = codeflash_output  # 2.79μs -> 2.92μs (4.32% slower)
    # Check that all coordinates are floats
    for pt in result:
        for val in pt:
            pass


# Edge Test Cases


def test_zero_width():
    # Rectangle with zero width
    codeflash_output = _calculate_quad_point_coordinates(2, 3, 0, 4, 0)
    result = codeflash_output  # 3.52μs -> 2.01μs (75.5% faster)
    expected = ((2, 3), (2, 3), (2, 7), (2, 7))
    tuples_close(result, expected)


def test_zero_height():
    # Rectangle with zero height
    codeflash_output = _calculate_quad_point_coordinates(2, 3, 4, 0, 0)
    result = codeflash_output  # 3.44μs -> 2.03μs (69.4% faster)
    expected = ((2, 3), (6, 3), (6, 3), (2, 3))
    tuples_close(result, expected)


def test_zero_width_and_height():
    # Rectangle with zero width and height
    codeflash_output = _calculate_quad_point_coordinates(2, 3, 0, 0, 0)
    result = codeflash_output  # 3.42μs -> 1.93μs (77.3% faster)
    expected = ((2, 3), (2, 3), (2, 3), (2, 3))
    tuples_close(result, expected)


def test_negative_width():
    # Rectangle with negative width
    codeflash_output = _calculate_quad_point_coordinates(0, 0, -2, 3, 0)
    result = codeflash_output  # 3.45μs -> 2.05μs (68.3% faster)
    expected = ((0, 0), (-2, 0), (-2, 3), (0, 3))
    tuples_close(result, expected)


def test_negative_height():
    # Rectangle with negative height
    codeflash_output = _calculate_quad_point_coordinates(0, 0, 2, -3, 0)
    result = codeflash_output  # 3.41μs -> 1.97μs (72.8% faster)
    expected = ((0, 0), (2, 0), (2, -3), (0, -3))
    tuples_close(result, expected)


def test_large_angle():
    # Angle greater than 360 degrees
    codeflash_output = _calculate_quad_point_coordinates(0, 0, 2, 3, 450)
    result = codeflash_output  # 3.69μs -> 3.87μs (4.68% slower)
    # 450 degrees == 90 degrees
    expected = ((0, 0), (0, 2), (-3, 2), (-3, 0))
    tuples_close(result, expected)


def test_negative_angle_large():
    # Angle less than -360 degrees
    codeflash_output = _calculate_quad_point_coordinates(0, 0, 2, 3, -450)
    result = codeflash_output  # 3.64μs -> 3.80μs (4.08% slower)
    # -450 degrees == -90 degrees
    expected = ((0, 0), (0, -2), (3, -2), (3, 0))
    tuples_close(result, expected)


def test_extremely_large_values():
    # Very large width and height
    x, y, w, h = 1e12, -1e12, 1e9, 2e9
    codeflash_output = _calculate_quad_point_coordinates(x, y, w, h, 0)
    result = codeflash_output  # 2.89μs -> 1.45μs (99.9% faster)
    expected = ((x, y), (x + w, y), (x + w, y + h), (x, y + h))
    tuples_close(result, expected)


def test_extremely_small_values():
    # Very small width and height
    x, y, w, h = 1e-12, -1e-12, 1e-9, 2e-9
    codeflash_output = _calculate_quad_point_coordinates(x, y, w, h, 0)
    result = codeflash_output  # 2.91μs -> 1.52μs (91.6% faster)
    expected = ((x, y), (x + w, y), (x + w, y + h), (x, y + h))
    tuples_close(result, expected)


def test_nan_inputs():
    # Inputs are NaN
    nan = float("nan")
    codeflash_output = _calculate_quad_point_coordinates(nan, nan, nan, nan, nan)
    result = codeflash_output  # 2.98μs -> 3.08μs (3.47% slower)
    for pt in result:
        for val in pt:
            pass


def test_many_rectangles_no_rotation():
    # Test with 1000 rectangles, no rotation
    for i in range(1000):
        x, y, w, h = i, i + 1, i + 2, i + 3
        codeflash_output = _calculate_quad_point_coordinates(x, y, w, h, 0)
        result = codeflash_output  # 651μs -> 440μs (47.9% faster)
        expected = ((x, y), (x + w, y), (x + w, y + h), (x, y + h))
        tuples_close(result, expected)


def test_many_rectangles_varied_rotation():
    # Test with 500 rectangles, rotation from 0 to 359 degrees
    for i in range(500):
        x, y, w, h = i * 0.5, i * 0.7, i * 0.3 + 1, i * 0.2 + 2
        angle = (i * 7) % 360
        codeflash_output = _calculate_quad_point_coordinates(x, y, w, h, angle)
        result = codeflash_output  # 288μs -> 275μs (4.64% faster)
        # All coordinates should be floats
        for pt in result:
            for val in pt:
                pass


def test_large_rectangle_at_large_angle():
    # Very large rectangle at a large angle
    x, y, w, h = 1e6, -1e6, 5e5, 2e5
    angle = 123.4
    codeflash_output = _calculate_quad_point_coordinates(x, y, w, h, angle)
    result = codeflash_output  # 2.89μs -> 2.91μs (0.653% slower)
    for pt in result:
        for val in pt:
            pass


def test_performance_many_calls():
    # Performance: Call the function 1000 times in a loop
    for i in range(1000):
        _calculate_quad_point_coordinates(
            i, i, i + 1, i + 2, i % 360
        )  # 674μs -> 625μs (7.82% faster)
    # If it completes, test passes


def test_rotations_full_circle():
    # Test with angles from 0 to 359 degrees for a single rectangle
    x, y, w, h = 10, 20, 30, 40
    for angle in range(360):
        codeflash_output = _calculate_quad_point_coordinates(x, y, w, h, angle)
        result = codeflash_output  # 242μs -> 223μs (8.10% faster)
        for pt in result:
            for val in pt:
                pass


# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.

To edit these changes git checkout codeflash/optimize-_calculate_quad_point_coordinates-miyj2c38 and push.

Codeflash Static Badge

The optimization achieves a **15% speedup** through two key improvements:

**1. Zero-angle fast path**: When `angle=0` (no rotation), the code bypasses expensive trigonometric calculations by directly setting `sin_angle=0.0` and `cos_angle=1.0`. This eliminates calls to `math.radians()`, `math.sin()`, and `math.cos()` for the common case of non-rotated rectangles.

**2. Computation reuse**: Pre-computes `height_sin`, `height_cos`, `width_cos`, and `width_sin` once, eliminating repeated multiplications in the coordinate calculations.

**Performance impact by use case**:
- **Zero-angle cases** (test results show 64-85% faster): Massive speedup when rectangles aren't rotated, which appears common in PDF rendering
- **Non-zero angles** (test results show 2-8% slower): Slight overhead from the conditional check and extra variable assignments, but minimal impact
- **Mixed workloads** (test results show 4-8% faster overall): The optimization pays off when zero-angle cases are frequent

**Context relevance**: The function is called from `_get_coordinates_of_block()` in PDF backend rendering, suggesting it's in a hot path for PDF generation. Since many PDF elements (text, images, shapes) are typically unrotated, the zero-angle fast path should provide significant real-world benefits for matplotlib's PDF output performance.

The optimization preserves exact mathematical behavior while trading a small conditional overhead for substantial gains in the common unrotated case.
@codeflash-ai codeflash-ai bot requested a review from mashraf-222 December 9, 2025 11:59
@codeflash-ai codeflash-ai bot added ⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: High Optimization Quality according to Codeflash labels Dec 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: High Optimization Quality according to Codeflash

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant