Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

jklymak
Copy link
Member

@jklymak jklymak commented Apr 24, 2025

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 marked this pull request as ready for review April 24, 2025 15:24
Copy link
Member

@dstansby dstansby left a comment

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 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
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 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
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

eta1, eta2 = np.deg2rad([eta1, eta2])

# 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
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
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
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.

print('Here')
else:
# this will not grow exponentially if we allow wrapping arcs:
n = int(2 * np.ceil(2 * np.abs(eta2 - eta1) / np.pi))
Copy link
Member

Choose a reason for hiding this comment

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

Looks like this is missing a test too.

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)


@image_comparison(['arc_close360'], style='default', remove_text=True,
extensions=['png'])
def test_arc_close360():
Copy link
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
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
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?

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
2 participants