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

Check for numeric type using numbers.Real #6802

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

Conversation

thangleiter
Copy link
Contributor

In several places code tests variables for numeric type using isinstance(value, (int, float)). This creates problems for native numpy types (among others I would presume). For example, isinstance(np.int32(0), int) evaluates to false.

Checking instead for numbers.Real should resolve this.

isinstance(np.int32(0), int) evaluates to false for example.
@thangleiter thangleiter requested a review from a team as a code owner January 17, 2025 11:52
Copy link

codecov bot commented Jan 27, 2025

Codecov Report

Attention: Patch coverage is 94.11765% with 1 line in your changes missing coverage. Please review.

Project coverage is 68.31%. Comparing base (76ae6ae) to head (c722257).
Report is 67 commits behind head on main.

Files with missing lines Patch % Lines
src/qcodes/math_utils/field_vector.py 75.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6802      +/-   ##
==========================================
- Coverage   69.37%   68.31%   -1.07%     
==========================================
  Files         340      341       +1     
  Lines       31341    31359      +18     
==========================================
- Hits        21744    21422     -322     
- Misses       9597     9937     +340     

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

@jenshnielsen
Copy link
Collaborator

Thanks @thangleiter

I am a bit torn on this. On one hand I fully agree with you.

On the other hand the numbers classes don't work well with type checkers.
See python/mypy#3186 (comment)
This is also the reason you ase seeing some failures in your test suite.
If possible I think my preference would be to try to get rid of these isinstance checks and transform input into the correct type when passed to a function or method.

Timing wise this is about 10 times slower than checking just for the build in types
but not that much slower than checking for both numpy and build in types.
I guess in most of these use cases that probably does not matter too much.
``
In [14]: %timeit isinstance(1, numbers.Real)
273 ns ± 23.3 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

In [15]: %timeit isinstance(1, int)
36 ns ± 1.7 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)

In [16]: %timeit isinstance(1, float)
56 ns ± 2.84 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)

In [17]: %timeit isinstance(1, (float, np.float64))
152 ns ± 9.11 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)

In [18]: %timeit isinstance(1, (float, np.floating))
219 ns ± 41.1 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

In [19]: %timeit isinstance(1, np.float64)
90.1 ns ± 3.46 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)

In [20]: %timeit isinstance(1, (int,float))
59.7 ns ± 4.21 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)





@thangleiter
Copy link
Contributor Author

I see, I wasn't aware of the unclear state of the numbers module.

If possible I think my preference would be to try to get rid of these isinstance checks and transform input into the correct type when passed to a function or method.

I think this is fine in drivers where you know which type you want to pass on. But the most important point where this comes up is get_ramp_values(), and there you'd need to carefully check validators etc to decide whether to cast to int or float (like in the step setter), which would definitely defeat the performance argument.

Since likely the numpy dtypes are the most relevant, how about introducing a NumbersType: TypeAlias = int | float | np.integer | np.floating and checking for that? That, by some magic, appears to also be very fast!

>>> %timeit isinstance(1, (int, float, np.integer, np.floating))
114 ns ± 2.95 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
>>> %timeit isinstance(1, NumbersType)
31.3 ns ± 0.763 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
>>> %timeit isinstance(1, int)
28.3 ns ± 0.556 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)

@jenshnielsen
Copy link
Collaborator

@thangleiter That sounds like a good solution until hopefully a better solution appears upstream.

@thangleiter
Copy link
Contributor Author

thangleiter commented Jan 27, 2025

Looks like this was still a problem for mypy. The FieldVector bit should is easily fixed by casting to float, but the permissive_range bit... There is a typing inconsistency in the code as is.

The step getter pretends to return a float, but the setter accepts integers as well. get_ramp_values() and permissive_range()
assume floats, however, so parameters with integer validators are not covered by the type annotations while they should work perfectly fine.

Tentatively typed step and those functions with the new NumberType, see if that works for you :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants