Skip to content
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

Add particlewise keyword to particle_collections.is_category #2648

Merged
merged 24 commits into from May 4, 2024

Conversation

jwreep
Copy link
Contributor

@jwreep jwreep commented Apr 25, 2024

Description

Addresses #2595 by adding the requested particlewise keyword to the function particle_collections.is_category.

This changes the default behavior to return a bool rather than a list of bool. If the original functionality is preferred to be the default, the default value of particlewise could be switched to True.

I've added tests that should (hopefully) address all the new cases, by using particlewise as both True and False and adding one new test that should evaluate to True.

I looked at the docs to see if any changes were needed, but the is_category function doesn't appear to have any further description there. Please let me know if I missed something!

Motivation and context

See the description by @namurphy in #2595. It was marked as "needs discussion", which I don't know if this has been resolved.

Related issues

#2595

@jwreep jwreep requested a review from a team as a code owner April 25, 2024 21:42
@jwreep jwreep requested review from namurphy and removed request for a team April 25, 2024 21:42
@jwreep
Copy link
Contributor Author

jwreep commented Apr 25, 2024

The ruff error failing is FBT003, from this line
return category_list if particlewise else np.allclose(category_list, True)
which I think is complaining about the call to np.allclose. An alternative formulation, which I think wouldn't trigger the warning, is np.allclose(category_list, np.array([True])) but that's ugly coding and the extra call to np.array will add a small bit of overhead.

There's some relevant discussion in this thread.

Any suggestions?

@jwreep
Copy link
Contributor Author

jwreep commented Apr 25, 2024

This caused a lot of other tests to fail because is_category appears to be used pretty commonly in the decorators. I'm guessing to avoid breaking lots of other parts of the code it would be better to switch the default to particlewise = True. Alternatively, we would have to carefully make sure to insert the keyword everywhere the function is called.

Any thoughts?

@namurphy
Copy link
Member

The ruff error failing is FBT003, from this line return category_list if particlewise else np.allclose(category_list, True) which I think is complaining about the call to np.allclose.

Perhaps we could add a # noqa: FBT003 comment at the end of the corresponding line so that ruff will ignore the rules on those lines?

@namurphy
Copy link
Member

namurphy commented Apr 29, 2024

This caused a lot of other tests to fail because is_category appears to be used pretty commonly in the decorators. I'm guessing to avoid breaking lots of other parts of the code it would be better to switch the default to particlewise = True. Alternatively, we would have to carefully make sure to insert the keyword everywhere the function is called.

In the long term, it would probably be best for Particle.is_category and ParticleList.is_category to return the same type by default. My inclination would be to insert the keyword everywhere the function is called. But, that could possibly be a follow-up PR. 🤔

Thank you for doing this!

Copy link

codecov bot commented Apr 30, 2024

Codecov Report

Attention: Patch coverage is 95.23810% with 1 lines in your changes are missing coverage. Please review.

Project coverage is 95.20%. Comparing base (820c300) to head (8fe0fd8).

Files Patch % Lines
src/plasmapy/particles/particle_collections.py 85.71% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2648      +/-   ##
==========================================
- Coverage   95.22%   95.20%   -0.02%     
==========================================
  Files         103      103              
  Lines        9401     9414      +13     
  Branches     2151     2156       +5     
==========================================
+ Hits         8952     8963      +11     
  Misses        274      274              
- Partials      175      177       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@jwreep
Copy link
Contributor Author

jwreep commented Apr 30, 2024

Thanks @namurphy!

I've added the suggested noqa replaced np.allclose(category_list, True) with all(category_list) and tried to update the decorator code to fix the issues. It looks like it's solved most of the problems. There's still one outstanding issue with AbstractPhysicalParticle which I'll come back to in a while (looks like it defines its own is_category).

I'm not sure that I understand the outstanding issue with the Documentation, though. Any insight into the issue there?

@jwreep
Copy link
Contributor Author

jwreep commented Apr 30, 2024

Sorry for the commits going back and forth. I'm trying to understand the issue with the static type checking.

I've changed ParticleList.is_category to return either bool or list[bool] which is throwing an error in the static type check because any(ParticleList) might not return an iterable. I have it nested under an if-statement that should determine whether it has returned a bool or list[bool], though, so I'm a bit confused why the issue arises.

Edit: still haven't figured this one out. It's raising an error in decorators.verify_allowed_types, which should work just fine (the function won't be called if it's not iterable), but the static type checking still raises an issue with it. Any suggestions would be very helpful. I'm not terribly familiar with the mypy checks.

@github-actions github-actions bot added the docs PlasmaPy Docs at http://docs.plasmapy.org label May 3, 2024
@jwreep
Copy link
Contributor Author

jwreep commented May 3, 2024

@namurphy, please review when you can. I finally managed to get the issues sorted.

Copy link
Member

@namurphy namurphy left a comment

Choose a reason for hiding this comment

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

LGTM! 🚀 Thank you for doing this! 🙏🏻

I only have a few quick suggestions, and I think we can get this merged in time for our v2024.5.0 release on Tuesday or Wednesday.

Thank you again!

changelog/2648.feature.rst Outdated Show resolved Hide resolved
changelog/2648.feature.rst Outdated Show resolved Hide resolved
Comment on lines +262 to +273
As with an individual |Particle| and |CustomParticle|, we can check whether
all the particles in a list fall within a category using |is_category|.

>>> helium_ions.is_category("ion")
False

We may also check each particle in the list individually by setting
the keyword ``particlewise`` to `True`.

>>> helium_ions.is_category("ion", particlewise=True)
[False, True, True]

Copy link
Member

Choose a reason for hiding this comment

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

👍🏻 Thank you for updating this here! I often forget to update the narrative docs until I realize it about six months later... 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I only caught this while trying to track down why the docs were failing to build

if not all(ions.is_category("ion")):
if not ions.is_category("ion"):
Copy link
Member

Choose a reason for hiding this comment

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

👍🏻 Always nice to be able to shorten/simplify code!

Comment on lines +317 to +335
@overload
def is_category(
self,
*category_tuple,
require: str | Iterable[str] | None = None,
any_of: str | Iterable[str] | None = None,
exclude: str | Iterable[str] | None = None,
) -> list[bool]:
particlewise: Literal[True] = ...,
) -> bool: ...

@overload
def is_category(
self,
*category_tuple,
require: str | Iterable[str] | None = None,
any_of: str | Iterable[str] | None = None,
exclude: str | Iterable[str] | None = None,
particlewise: Literal[False],
) -> list[bool]: ...
Copy link
Member

Choose a reason for hiding this comment

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

👍🏻 Nice use of @overload!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So, I did this when I was trying to solve my issues with the static typing. It didn't solve that issue, but from what I gather reading comments on StackExchange and Github, it seems this is a clear way to specify the output when it changes based on a bool input.

Copy link
Member

Choose a reason for hiding this comment

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

That is helpful to know!

If you run into any mypy errors that feel unduly strict, please feel free to bring it up in #2589. We're still in the process of figuring out how strict we want the mypy settings to be, since there are tradeoffs between the benefits of type hints compared to the effort needed to implement them. I suspect that we'll probably need to make mypy somewhat less strict.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I had to use # type: ignore[arg-type] to resolve the issue here:

        if (
            not self.allow_custom_particles
            and isinstance(particle, ParticleList)
            and any(particle.is_category("custom", particlewise=True))  # type: ignore[arg-type]
        ):

The third condition can only be called if it is indeed a ParticleList, but the mypy check insisted that there would be an issue with possibly return something that is not iterable, which means that the call to any() would fail. It might be too strict for this case? I'll mention it in the other thread.

@github-actions github-actions bot added the changes existing API Involves or proposes breaking (backward incompatible) changes label May 4, 2024
@namurphy
Copy link
Member

namurphy commented May 4, 2024

Thank you for addressing the changes! Everything looks ready, so I'll enable auto-merge.

@namurphy namurphy enabled auto-merge (squash) May 4, 2024 01:37
@jwreep
Copy link
Contributor Author

jwreep commented May 4, 2024

Thanks for the help!

@namurphy namurphy merged commit ca92246 into PlasmaPy:main May 4, 2024
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
changes existing API Involves or proposes breaking (backward incompatible) changes docs PlasmaPy Docs at http://docs.plasmapy.org testing
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants