Skip to content

FIX: update arc to do better rounding and allow wrap#29962

Closed
jklymak wants to merge 9 commits into
matplotlib:mainfrom
jklymak:mnt-fix-arc
Closed

FIX: update arc to do better rounding and allow wrap#29962
jklymak wants to merge 9 commits into
matplotlib:mainfrom
jklymak:mnt-fix-arc

Conversation

@jklymak

@jklymak jklymak commented Apr 24, 2025

Copy link
Copy Markdown
Member

closes #29953

This actually fixes the numerical instability at least as in #29953. Also added the option, *wrap* for larger arcs, but is optional, and the original behaviour is still default. Updated the docstring to be Numpy compatible.

The new test shows that the new algorithm is a bit less fragile to numerical slop (presumably because np.mod is better with this, or at least we are standard compliant.

Note that some tests tested that if theta2 < theta1 it stayed that way. So theta1=-90 and theta2=-110 has an arc that goes 320 degrees from -90 to +250, rather than one that goes from -90 to -110. eg the arc always goes counter clockwise. I've updated the docs to make this clear.

@jklymak jklymak marked this pull request as draft April 24, 2025 00:26
@jklymak jklymak force-pushed the mnt-fix-arc branch 2 times, most recently from ffdd0cf to 14d5441 Compare April 24, 2025 15:23
@jklymak jklymak marked this pull request as ready for review April 24, 2025 15:24

@dstansby dstansby left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good overall, but:

  • Some code paths need test coverage
  • I think the image test could compare two generated images, instead of being a full on figure test with a new test image
  • Lots of stray print() statements
  • I also left some suggestions for minor doc improvements

Comment thread lib/matplotlib/path.py Outdated
Comment on lines +961 to +963
(*theta2*) of the arc. If the arc spans more than 360 degrees, it
will be wrapped to fit within the range from *theta1* to *theta1* +
360, provided *wrap* is True. The arc is drawn counter-clockwise

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
(*theta2*) of the arc. If the arc spans more than 360 degrees, it
will be wrapped to fit within the range from *theta1* to *theta1* +
360, provided *wrap* is True. The arc is drawn counter-clockwise
(*theta2*) of the arc. If the arc spans more than 360 degrees, it
will be wrapped to start at *theta1* and have a total extent <= 360 degrees,
provided *wrap* is True. The arc is drawn counter-clockwise

I think this is a bit clearer than talking about theta1 + 360?

Comment thread lib/matplotlib/path.py Outdated
Comment on lines +983 to +984
If True, the arc is wrapped to fit between *theta1* and *theta1* +
360 degrees. If False, the arc is not wrapped. The arc will be

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
If True, the arc is wrapped to fit between *theta1* and *theta1* +
360 degrees. If False, the arc is not wrapped. The arc will be
If True, the arc is wrapped to start at *theta1* and have an extent <= 360 degrees.
If False, the arc is not wrapped. The arc will be

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docs are rewritten. Hopefully clearer

Comment thread lib/matplotlib/path.py Outdated
Comment thread lib/matplotlib/path.py Outdated
Comment thread lib/matplotlib/path.py Outdated
Comment thread lib/matplotlib/path.py Outdated
# number of curve segments to make
if n is None:
n = int(2 ** np.ceil((eta2 - eta1) / halfpi))
if np.abs(eta2 - eta1) <= 2.2 * np.pi:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why 2.2pi here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's exactly 2pi some tests fail. 2.2 pi doesn't really matter because it deals with pi/2 increments. I could make it 2.25 pi?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does 2 * np.pi + 1e-3 or something similar work? Writing it like this makes it more obvious that the intention is 2pi + a tiny bit for numerical reasons.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure!

Comment thread lib/matplotlib/path.py Outdated
Comment thread lib/matplotlib/path.py
Comment thread lib/matplotlib/tests/test_path.py Outdated
Comment thread lib/matplotlib/tests/test_path.py Outdated

@image_comparison(['arc_close360'], style='default', remove_text=True,
extensions=['png'])
def test_arc_close360():

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you do this as a compare images test, instead of a figure test that adds a new figure?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how to make the requisite arcs without calling arc so I'm not sure what compare figure test would do? I could just make a full circle at exactly 360 degrees, but I'm not sure what that is testing.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this test, can you compare what you're generating on ax[0] and ax[1] against each other, and then compare what you're plotting on ax[2] against an empty axes?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see how to do that. I agree we should not have too many image tests, but sometimes they are clearer

@dstansby dstansby Feb 17, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of this one test, my suggestion is to have two tests:

  1. one which compares the code currently on ax[0] and ax[1] (because we expect them to be the same)
  2. one that compares the code currently on ax[2] to an empty axes (because we expect the code to not draw anything)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread lib/matplotlib/tests/test_path.py Outdated
Comment thread lib/matplotlib/path.py Outdated
Comment thread lib/matplotlib/path.py Outdated
@QuLogic

QuLogic commented Jun 12, 2026

Copy link
Copy Markdown
Member

The original issue was closed by #31844, but there is still some useful work to the docstrings here. Do you want to rebase and merge those?

@QuLogic QuLogic marked this pull request as draft June 12, 2026 02:58
@jklymak jklymak closed this Jun 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: numerical instability of Path.arc for 0 degree or 360 degree arc

3 participants