Skip to content

PoC: Generic-based Physical Type hints for Quantity#19149

Draft
AbhiLohar wants to merge 1 commit intoastropy:mainfrom
AbhiLohar:quantity-typing-poc
Draft

PoC: Generic-based Physical Type hints for Quantity#19149
AbhiLohar wants to merge 1 commit intoastropy:mainfrom
AbhiLohar:quantity-typing-poc

Conversation

@AbhiLohar
Copy link

This is a Proof of Concept (PoC) exploring the Generic approach for Quantity as discussed in #19034.
By inheriting from Generic[D], we allow static type checkers to see the physical dimension of a Quantity. Locally, this successfully catches unit mismatches that Annotated currently misses:

Screenshot 2026-01-14 011201

This implementation avoids using unit instances as types, addressing the concern that "units are not types".

@github-actions github-actions bot added the units label Jan 13, 2026
@github-actions
Copy link
Contributor

Thank you for your contribution to Astropy! 🌌 This checklist is meant to remind the package maintainers who will review this pull request of some common things to look for.

  • Do the proposed changes actually accomplish desired goals?
  • Do the proposed changes follow the Astropy coding guidelines?
  • Are tests added/updated as required? If so, do they follow the Astropy testing guidelines?
  • Are docs added/updated as required? If so, do they follow the Astropy documentation guidelines?
  • Is rebase and/or squash necessary? If so, please provide the author with appropriate instructions. Also see instructions for rebase and squash.
  • Did the CI pass? If no, are the failures related? If you need to run daily and weekly cron jobs as part of the PR, please apply the "Extra CI" label. Codestyle issues can be fixed by the bot.
  • Is a change log needed? If yes, did the change log check pass? If no, add the "no-changelog-entry-needed" label. If this is a manual backport, use the "skip-changelog-checks" label unless special changelog handling is necessary.
  • Is this a big PR that makes a "What's new?" entry worthwhile and if so, is (1) a "what's new" entry included in this PR and (2) the "whatsnew-needed" label applied?
  • At the time of adding the milestone, if the milestone set requires a backport to release branch(es), apply the appropriate "backport-X.Y.x" label(s) before merge.

@nstarman
Copy link
Member

@AbhiLohar, thanks for the PR. This does highlight one of the challenges of parametrizing by the dimension — we need a new dimension class for every possible dimension. E.g. PhysicalType("amount of substance") / PhysicalType("angular acceleration") would require generating a new class. If we limited ourselves to exponentiations of the SI base dimensions then maybe we could parametrize by a tuple of the exponents, e.g. Quantity[(1, 2, 5, 6)] would be dimensions = kg^1, m^2, etc. If we make a 1-to-1 mapping between unique string names and their dimension objects, we could maybe use Literal. IDK; this hard; like shape typing.

@nstarman nstarman requested review from eerovaher and mhvk January 13, 2026 22:26
@mhvk
Copy link
Contributor

mhvk commented Jan 14, 2026

For shape typing, see progress mentioned at https://blog.scientific-python.org/numpy/fellowship-program-2025-retrospective/#the-shape-typing-frontier-numtype - though looking at https://github.com/numpy/numtype/blob/main/src/_numtype/_shape.pyi, the shape-supporting part is still more like (numerical) dimensionality support -- I do not see how that, so far, could be easily used to support things like a shape ending in 3 or so.

For us, in principle I guess it is possible to make physical type instances classes instead. I suspect, though, that this problem will go away, as surely there are other classes where equivalents of Literal would be handy...

More relevantly, perhaps: what exactly does the current implementation catch? Say I have a function f(t : Quantity[Time]), would mypy catch that f(10*u.m) is bad and f(10*u.s) is OK?

@AbhiLohar
Copy link
Author

Hi @mhvk, I ran the test case you suggested.

Currently, Mypy returns Success for f(10 * u.m) even when f expects Quantity[Time]. This confirms that the current PoC only enforces type safety when the user explicitly annotates the variable. The 'blind spot' is that unit multiplication doesn't yet know how to return a specific Quantity[D].

@nstarman, this really highlights why the 'mapping' or 'exponent tuple' approach you mentioned is the logical next step. To make this work for end-users, we would need to:

  1. Map unit instances (like u.m) to their physical types (like Length).
  2. Use @overload on the multiplication operators so that int * Unit returns the correctly parametrized Quantity[D].

I'm going to look into how we might implement a trial @overload to see if we can bridge this gap!

Thank you!

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.

4 participants