From 19c0e07558f68de4970c56d67a23f0ad61ac5238 Mon Sep 17 00:00:00 2001 From: stevezhang1999 Date: Tue, 3 Oct 2023 20:48:25 -0400 Subject: [PATCH 1/2] fix floating comparison in path.arc --- lib/matplotlib/path.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/path.py b/lib/matplotlib/path.py index e72eb1a9ca73..67ee10e8616b 100644 --- a/lib/matplotlib/path.py +++ b/lib/matplotlib/path.py @@ -949,7 +949,10 @@ def arc(cls, theta1, theta2, n=None, is_wedge=False): eta2 = theta2 - 360 * np.floor((theta2 - theta1) / 360) # Ensure 2pi range is not flattened to 0 due to floating-point errors, # but don't try to expand existing 0 range. - if theta2 != theta1 and eta2 <= eta1: + # theta1 != theta2 and eta2 <= eta1 + if (not np.isclose(theta2, theta1) and + (np.isclose(eta2, eta1) or + (not np.isclose(eta2, eta1) and eta2 < eta1))): eta2 += 360 eta1, eta2 = np.deg2rad([eta1, eta2]) From 2196b0f865319e47fdbbc92ceec2d0696f53c226 Mon Sep 17 00:00:00 2001 From: stevezhang1999 Date: Wed, 4 Oct 2023 23:17:56 -0400 Subject: [PATCH 2/2] handle edge case when theta1=0; add tests --- lib/matplotlib/path.py | 12 ++++++++---- lib/matplotlib/tests/test_path.py | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/path.py b/lib/matplotlib/path.py index 67ee10e8616b..086a1207dbd1 100644 --- a/lib/matplotlib/path.py +++ b/lib/matplotlib/path.py @@ -943,16 +943,20 @@ def arc(cls, theta1, theta2, n=None, is_wedge=False): polylines, quadratic or cubic Bezier curves `_. """ + # we want to get the same result when the input changes within [-tol, tol] + # this behavior is only for internal use to offset floating-point error + # so it should not be relied on by the user + tol = 1e-6 + halfpi = np.pi * 0.5 eta1 = theta1 eta2 = theta2 - 360 * np.floor((theta2 - theta1) / 360) # Ensure 2pi range is not flattened to 0 due to floating-point errors, # but don't try to expand existing 0 range. - # theta1 != theta2 and eta2 <= eta1 - if (not np.isclose(theta2, theta1) and - (np.isclose(eta2, eta1) or - (not np.isclose(eta2, eta1) and eta2 < eta1))): + # condition: theta1 != theta2 and eta2 <= eta1 + # implementation note: use np.isclose() may break when theta1 = 0 + if abs(theta1 - theta2) >= tol and (eta2 <= eta1 + tol): eta2 += 360 eta1, eta2 = np.deg2rad([eta1, eta2]) diff --git a/lib/matplotlib/tests/test_path.py b/lib/matplotlib/tests/test_path.py index 8c0c32dc133b..a475b10c7755 100644 --- a/lib/matplotlib/tests/test_path.py +++ b/lib/matplotlib/tests/test_path.py @@ -475,6 +475,22 @@ def test_full_arc(offset): np.testing.assert_allclose(maxs, 1) +@pytest.mark.parametrize('offset', range(-720, 361, 45)) +def test_full_arc_rounding(offset): + # GH 26972 - edge case for floating point rounding + # we want get the same result when input of arc + # changes within [-tol, tol], where tol = 1e-6 + tol = 1e-6 + low = offset + high = 360 + offset + tol + + path = Path.arc(low, high) + mins = np.min(path.vertices, axis=0) + maxs = np.max(path.vertices, axis=0) + np.testing.assert_allclose(mins, -1) + np.testing.assert_allclose(maxs, 1) + + def test_disjoint_zero_length_segment(): this_path = Path( np.array([