Skip to content

Add NumPy number types to TParamVal - #8236

Open
sankalpsthakur wants to merge 14 commits into
quantumlib:mainfrom
sankalpsthakur:fix/5758-tparamval-numpy
Open

Add NumPy number types to TParamVal#8236
sankalpsthakur wants to merge 14 commits into
quantumlib:mainfrom
sankalpsthakur:fix/5758-tparamval-numpy

Conversation

@sankalpsthakur

@sankalpsthakur sankalpsthakur commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Fixes #5758.

Summary

TParamVal was defined as float | sympy.Expr, while parameter values such as EigenGate exponents already accept NumPy scalars (e.g. np.double, np.short) at runtime. This change adds np.number so the type alias matches that behavior, consistent with TParamValComplex which already includes np.number.

TParamVal = float | np.number | sympy.Expr

Notes

  • Does not redesign the parameter system (no new TParamValInput / conversion layer).
  • Does not move Scalar from linear_dict.py (orthogonal follow-up if desired).
  • Removes the # TODO(#5758) marker next to existing serializer tests that already cover np.double / np.short exponents.
  • Adds an np.number TypeVar overload on canonicalize_half_turns.
  • Adds NumPy scalar cases to canonicalize_half_turns and ParamResolver tests.

Test plan

  • pytest cirq-core/cirq/value/angle_test.py cirq-core/cirq/protocols/resolve_parameters_test.py cirq-core/cirq/ops/eigen_gate_test.py cirq-core/cirq/study/resolver_test.py
  • Existing cirq-google serializer cases already exercise NumPy exponents end-to-end

AI/LLM disclosure

  • AI coding tools (including Grok and/or Codex agent-assisted editing) were used to help draft or modify code and this PR description.
  • I reviewed the complete change, understand the reasoning, and ran the reported local tests before submitting.
  • This submission is original work of authorship under the project CLA / contributor terms; AI output was not pasted unreviewed.

@github-actions github-actions Bot added the size: S 10< lines changed <50 label Aug 1, 2026
@codecov

codecov Bot commented Aug 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.59%. Comparing base (cfa8a84) to head (03fe419).

Additional details and impacted files
@@           Coverage Diff            @@
##             main    #8236    +/-   ##
========================================
  Coverage   99.59%   99.59%            
========================================
  Files        1125     1126     +1     
  Lines      103250   103679   +429     
========================================
+ Hits       102829   103258   +429     
  Misses        421      421            

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

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@mhucka mhucka left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thank you for this work.

To go along with these changes, an additional overload for canonicalize_half_turns for np.number (or TypeVar bounded by np.number) is needed in file cirq/value/angle.py.

The test cases also should be expanded to exercise both floating-point and integer NumPy scalar subtypes (e.g., np.float64, np.double, np.int64, np.short) to verify that arithmetic operations (e.g., modulo operations, and range wrapping into (-1, +1]) preserve type and value invariants as expected across NumPy numeric types.

Finally, Dax Fohl noted in the last comment on issuue 5758 that a change of this nature would have backward compatibility implications. Can you add test cases (e.g., for the case mentioned at the end of the first paragraph in that comment) to verify that backards compatibility is maintained?

@mhucka mhucka added triage/discuss Needs decision / discussion, bring these up during Cirq Cynque area/numpy labels Aug 18, 2026
@mhucka

mhucka commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Discussed during Cirq Cynq 2026-08-19: consensus is that this change may indeed work, but this definitely needs many more test cases than what is in the PR currently. In particular, we need to check that the parameter resolver works correctly. (See resolve_parameter in cirq/protocols/resolver_parameter.py). The param resolver needs to be able to correctly resolve a bunch of different gates.

A possible approach is to look at the tests already in param resolver, then extend the relevant tests with numpy values, and make sure everything still works.

@sankalpsthakur

Copy link
Copy Markdown
Contributor Author

Added an np.number TypeVar overload on canonicalize_half_turns, plus wrapping tests for np.float64/np.double/np.int64/np.short, ParamResolver coverage on X/Y/Z/H/CZ/CX, and the XPowGate(np.double(0.5)) isinstance(float) case from #5758. 987c1a3, please take another look.

@github-actions github-actions Bot added size: M 50< lines changed <250 and removed size: S 10< lines changed <50 labels Aug 19, 2026
@sankalpsthakur
sankalpsthakur force-pushed the fix/5758-tparamval-numpy branch from 987c1a3 to b54346b Compare August 19, 2026 20:42
@sankalpsthakur

Copy link
Copy Markdown
Contributor Author

Rebased onto main and fixed format in b54346b.

@sankalpsthakur
sankalpsthakur force-pushed the fix/5758-tparamval-numpy branch from b54346b to d4780eb Compare August 20, 2026 01:02
@sankalpsthakur

Copy link
Copy Markdown
Contributor Author

Rebased onto main in d4780eb.

@github-actions github-actions Bot added size: L 250< lines changed <1000 and removed size: M 50< lines changed <250 labels Aug 20, 2026
@sankalpsthakur

sankalpsthakur commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

@mhucka added wrapping tests for np.float32, np.float64, np.int32, and np.int64 on canonicalize_half_turns (type preserved into (-1, +1]), plus ParamResolver coverage on more EigenGates. The #5758 np.double isinstance(float) case is still there. Overload is a TypeVar bound by np.number.

@sankalpsthakur

Copy link
Copy Markdown
Contributor Author

Extended the existing resolve_parameters / is_parameterized / parameter_names / recursive tests with NumPy scalars (np.float32, np.float64, np.double, np.int32, np.int64, np.short). ParamResolver formulas and gates: rx/ry/rz, FSim, PhasedXZ, GlobalPhase, Wait, CCZ/CCX, controlled XPow.

f22606b

PYTHONPATH=cirq-core pytest cirq-core/cirq/protocols/resolve_parameters_test.py cirq-core/cirq/study/resolver_test.py cirq-core/cirq/ops/eigen_gate_test.py cirq-core/cirq/value/angle_test.py

546 passed

@sankalpsthakur

Copy link
Copy Markdown
Contributor Author

added a parametrized test documenting the isinstance(exponent, float) split: true for np.float64/np.double, false for np.float32 and the int dtypes, same before and after this pr since widening TParamVal is annotation-only. also locks in that resolve_parameters normalizes every dtype to plain float regardless.

@mhucka mhucka left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thank you for this effort!

I have some initial review comments.

Comment thread cirq-core/cirq/value/type_alias.py Outdated

@mhucka mhucka left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the updates!

Could some additional tests be added for the following too?

  1. Verify roundtrip equality for gates and circuits parameterized with various NumPy scalar dtypes. (E.g., cirq.to_json() must serialize gates with NumPy scalar parameters without throwing TypeError, and cirq.read_json() needs to reconstruct them with standard Python numeric types.)

  2. Check that cirq.Gate.with_probability(p) and channel classes (such as cirq.DepolarizingChannel, cirq.BitFlipChannel, etc.) accept TParamVal or call validate_probability.

  3. Check that canonicalize_half_turns handles unsigned integer types (such as half_turns -= 2 when values exceed 1) without overflow or type errors. Maybe add test cases that use types np.uint8, np.uint16, np.uint32, np.uint64.

  4. Check what happens with 0-dimensional np.ndarrays.

  5. Check floating-point value edge cases: signed zero, NaN, Inf. For example, checking that canonicalize_half_turns(np.float64(-0.0)) is handled without creating negative zero mismatches in gate equality, checking that cirq.is_parameterized(cirq.XPowGate(exponent=np.nan)) returns False, and so on.

@mhucka

mhucka commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

@pavoljuhas and/or @daxfohl, would you be able to take a look at this PR too?

@mhucka mhucka added status/awaiting-response Waiting on a response from someone and removed triage/discuss Needs decision / discussion, bring these up during Cirq Cynque labels Sep 2, 2026
@sankalpsthakur
sankalpsthakur force-pushed the fix/5758-tparamval-numpy branch from 1717243 to 5066cf8 Compare September 4, 2026 04:19
@mhucka mhucka self-assigned this Sep 4, 2026
TParamVal was float | sympy.Expr, but parameter values (e.g. EigenGate
exponents) already accept NumPy scalars like np.double at runtime.
Mirror TParamValComplex by including np.number so static type checkers
agree with the established runtime behavior.

Fixes quantumlib#5758

Signed-off-by: Sankalp Thakur <sankalphimself@gmail.com>
sankalpsthakur and others added 12 commits September 5, 2026 18:42
Signed-off-by: Sankalp Thakur <sankalphimself@gmail.com>
Pytest Ubuntu (3.14) failed on stream_manager_test cancel race;
Mac/Win 3.14 and other Ubuntu versions passed. PR only touches TParamVal.
Add an np.number TypeVar overload for canonicalize_half_turns and cover
float/integer NumPy scalars, ParamResolver, and the quantumlib#5758 isinstance case.

Signed-off-by: Sankalp Thakur <sankalphimself@gmail.com>
Signed-off-by: Sankalp Thakur <sankalphimself@gmail.com>
Cover np.float32/np.int32 through canonicalize_half_turns wrapping into
(-1, +1], TParamVal, ParamResolver, and the quantumlib#5758 np.double isinstance case.

Signed-off-by: Sankalp Thakur <sankalphimself@gmail.com>
Add NumPy values to the existing resolve_parameters, is_parameterized,
parameter_names, and recursive resolve tests. Cover ParamResolver
formulas plus rx/ry/rz, FSim, PhasedXZ, GlobalPhase, Wait, CCZ/CCX,
and controlled XPow.

Signed-off-by: Sankalp Thakur <sankalphimself@gmail.com>
Backward compat only holds for np.float64/np.double, whose NumPy scalar
type subclasses Python float; np.float32 and the integer dtypes do not.
This was already true before TParamVal was widened (widening the alias
is annotation-only), but was implicit. Make it an explicit, parametrized
test so the boundary is documented rather than assumed.

Also covers that resolve_parameters normalizes every dtype to a plain
float on resolution, regardless of the pre-resolution isinstance result.
Serialize gates and circuits parameterized with NumPy scalar dtypes
through cirq.to_json/read_json as Python numbers. Accept those scalars
on Gate.with_probability and channel classes that call validate_probability.

Signed-off-by: Sankalp Thakur <sankalphimself@gmail.com>
@sankalpsthakur
sankalpsthakur force-pushed the fix/5758-tparamval-numpy branch from 21b6491 to df77245 Compare September 5, 2026 14:46
Move TParamVal membership checks into type_alias_test.py, document that
the canonicalize_half_turns TypeVar excludes complex np.number types,
and wrap 0-d arrays through (-1, +1].

Co-authored-by: Sankalp Thakur <sankalpsthakur@users.noreply.github.com>
@sankalpsthakur

Copy link
Copy Markdown
Contributor Author

Addressed the remaining review notes on this tip (03fe4191):

  • TParamVal stays real NumPy scalars only (not complex np.number)
  • canonicalize_half_turns overload for NumPy real scalars
  • wrap tests for np.float64 / np.double / np.int64 / np.short
  • back-compat for XPowGate(exponent=np.double(0.5))
  • targeted suite: 670 passed

Ready for another look when you have time.

@daxfohl

daxfohl commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator

Should be fine. It's just a type change so it's not actually breaking any runtime behavior. The two backwards incompatibilities are

  1. Passing e.g. gate.exponent to a function that only takes float | sympy.Expr. But that seems unusual; most users would type their parameter as TParamVal to match what they're passing in. And the only breakage this case would cause is type checking; runtime would be fine.
  2. If a user's function does if isinstance(gate.exponent, float) somewhere that now needs to take numpy types into account. Those functions will now have broken runtime behavior when presented with numpy types, so is the larger problem. However, that's mostly mitigated by the fact that breaking it would require users to start passing in numpy types, which, they currently aren't. So this change wouldn't break anything that's not already broken. The biggest concern would be if there are third-party libraries that have e.g. if isinstance(gate.exponent, float) everywhere; for any user that uses both cirq and those third-party libraries, they'd have to patch the libraries in order to be compatible. Or they could just avoid using numpy types if they're using such a library, which is what they're already doing.

So, formally if we're doing tight type controls, this is breaking. But I can't think of any scenarios where it would cause any real-world problems. LGTM.

@sankalpsthakur

Copy link
Copy Markdown
Contributor Author

The remaining macOS failure is test_parallel_two_qubit_xeb_with_ideal_target: sampled fidelities [0.710634, 0.547693] missed the 0.9 ± 0.3 tolerance. Failed job. Could a maintainer rerun it? GitHub denied my rerun request because it requires repository admin rights.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/numpy size: L 250< lines changed <1000 status/awaiting-response Waiting on a response from someone

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Consider adding numpy number types to TParamVal

4 participants