Skip to content

Bind ExpH/ExpM with per-dtype overloads + Python-layer fixes (toward clean stubtest) - #915

Merged
yingjerkao merged 9 commits into
masterfrom
claude/fix-stubtest-errors
Jul 17, 2026
Merged

Bind ExpH/ExpM with per-dtype overloads + Python-layer fixes (toward clean stubtest)#915
yingjerkao merged 9 commits into
masterfrom
claude/fix-stubtest-errors

Conversation

@IvanaGyro

@IvanaGyro IvanaGyro commented Jun 18, 2026

Copy link
Copy Markdown
Member

Context

Follows #912 (now merged), which shipped the committed PEP 561 stubs generated against the pinned pybind11 3.0.4; this PR is based directly on master including it. Works toward a clean mypy.stubtest run against cytnx.cytnx. stubtest's dominant complaint is the overload-cannot-match errors: many scalar-accepting functions are bound once per C++ scalar dtype, and pybind11-stubgen renders several distinct C++ types onto the same Python type, producing duplicate @typing.overload signatures. This PR is the first increment (ExpH/ExpM); it does not make stubtest fully green yet (see "Remaining").

What this PR does

  • Bind linalg.ExpH/ExpM with one overload per Python-distinguishable dtype. Each function was bound ~20× per C++ scalar dtype, and stubgen collapses several of those onto the same Python type, so the stub carried duplicate overloads. Keep one binding per Python-distinguishable dtype and drop the dead/redundant ones:

    • A Python float/complex already binds to the cytnx_double/cytnx_complex128 overloads, and np.float64/np.complex128 are builtin subclasses caught by those same overloads, so cytnx_float, cytnx_complex64, numpy_scalar<float> and numpy_scalar<complex64> were unreachable duplicates.
    • The fixed-width integer overloads all accept the same Python int, so replace them with a single py::int_ overload that dispatches on magnitude to the int64/uint64 kernel — one signature covering the full arbitrary-precision Python-int range that no single fixed-width C++ overload can.

    Under pybind11 3.0.4 the kept overloads render as the numpy.* scalar types, int (py::int_), typing.SupportsFloat | typing.SupportsIndex (cytnx_double), and typing.SupportsComplex | typing.SupportsFloat | typing.SupportsIndex (cytnx_complex128). The complex128 annotation is a supertype of the narrower ones, but it is registered last, so each earlier overload stays reachable and linalg.pyi is mypy-clean for ExpH/ExpM (no overload-cannot-match).

  • Keep the scalar defaults valid at the binding site, not via a stub rewrite. Each b default is passed through py::arg_v with an explicit repr that stubgen renders as an elided ..., valid against any annotation, for two reasons:

    • the numpy_scalar overloads use a numpy.<type>(...) repr — otherwise pybind11 formats the default from numpy's own repr (np.<type>(...) under numpy 2.x), an unimported np alias that mypy rejects;
    • the cytnx_complex128 overload uses a literal ... repr — pybind11 3.0.4's SupportsComplex | SupportsFloat | SupportsIndex annotation excludes the builtin complex (typeshed's complex has no __complex__), so a literal 0j default would be flagged as an incompatible default, and a numpy.complex128(...) repr would misdescribe an overload that accepts a plain Python complex.

    The defaults still exist at runtime, so stubtest is unaffected and tools/generate_stubs.py needs no special-casing.

Numerical note / issue #914

linalg.Exp returns incorrect values for complex64 (ComplexFloat) tensors, which both ExpH and ExpM depend on. To preserve behaviour, numpy complex64 scalars are routed through the complex128 kernel; this is documented in code and tracked in #914, and the reroute can be removed once the kernel is fixed.

Remaining (follow-up PR)

After this PR linalg.pyi is mypy-clean, but stubtest still cannot run a clean comparison: ~5599 overload-cannot-match errors remain, now all in __init__.pyi — the Tensor/UniTensor operators (__add__, __mul__, __eq__, in-place/reverse variants, __setitem__, …), each bound ~24× per dtype, plus the generated __hash__/__eq__ markers. They need the same binding-site treatment this PR applies to ExpH/ExpM; cleaning them up is the bulk of the work and a dedicated follow-up (tracked in #928), after which CI can run stubtest.

Testing

  • pytest pytests/linalg_exp_test.py: 26 passed — ExpH/ExpM accept every supported Python and numpy scalar dtype individually and in mixed (a, b) pairs, matched against a NumPy eigendecomposition reference, including the complex64→complex128 case.
  • linalg.pyi is mypy-clean: python -m mypy.stubtest cytnx.cytnx reports zero errors for it, and regenerating the stubs is idempotent (the arg_v defaults reproduce the committed .pyi byte-for-byte).
  • C++ gtest suites were not rebuilt: this PR changes only pybind glue; the C++ ExpH/ExpM kernels are untouched.

🤖 Generated with Claude Code

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request simplifies and consolidates the Python bindings for ExpH and ExpM by replacing numerous type-specific overloads with a single overload accepting cytnx::Scalar, supported by implicit conversions for NumPy scalars and a C++ dispatch helper. It also cleans up unused imports, refactors type stubs, and adds unit tests for the new scalar handling. The review feedback highlights two key improvement opportunities: first, the return_indices parameter in getDegeneracy is currently ignored and should be respected; second, py::numpy_scalar<bool> should be registered for implicit conversion to Scalar to prevent runtime type errors with NumPy boolean scalars.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread cytnx/Bond_conti.py Outdated
Comment thread pybind/scalar_py.cpp Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6f1f3cc9d3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pybind/linalg_py.cpp Outdated
@IvanaGyro
IvanaGyro marked this pull request as draft June 18, 2026 16:50

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4fb440de6f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cytnx/cytnx/linalg.pyi Outdated
@IvanaGyro

Copy link
Copy Markdown
Member Author

I didn't really know what mypy's stubtest tests and tlthe best solution either. This PR may not correct.

@IvanaGyro
IvanaGyro force-pushed the claude/fix-stubtest-errors branch from 94d54c9 to bb32d2d Compare June 19, 2026 19:30
@IvanaGyro
IvanaGyro force-pushed the claude/pybind11-stubgen-stubs-ebny1c branch from c7473d7 to 6700547 Compare June 20, 2026 01:20
@IvanaGyro
IvanaGyro force-pushed the claude/fix-stubtest-errors branch from bb32d2d to 5bd01bb Compare June 20, 2026 01:20
@IvanaGyro IvanaGyro changed the title Bind ExpH/ExpM with one Scalar overload + Python-layer fixes (toward clean stubtest) Bind ExpH/ExpM with per-dtype overloads + Python-layer fixes (toward clean stubtest) Jun 20, 2026

Copy link
Copy Markdown
Member Author

Heads-up from running the full pytest pytests/ on the rebased stack — not caused by this PR, flagging for awareness:

pytests/example/TDVP/tdvp1_dense_test.py::test_tdvp1_dense fails its tolerance:

error = np.abs(Es[-1] - (-1.0 * Nsites)) = 1.72e-4
assert error < 1e-6

This is unrelated to the changes here: TDVP drives time evolution through linalg.Lanczos_Exp, not the ExpH/ExpM bindings this PR touches, and the test passed before rebasing onto current master. master reworked Lanczos_Exp since this branch's fork point (#889 "Fix TDVP Lanczos_Exp instability", #892 "Fix Lanczos_Exp warning at full Krylov dimension", and "Fix Lanczos_Exp one-dimensional Krylov spaces"), which governs this result. Worth tracking separately on master — in particular whether the 1e-6 tolerance is still appropriate after those changes.

— Claude


Generated by Claude Code

@IvanaGyro
IvanaGyro force-pushed the claude/pybind11-stubgen-stubs-ebny1c branch from 6700547 to bffc010 Compare June 20, 2026 03:30
@IvanaGyro
IvanaGyro force-pushed the claude/fix-stubtest-errors branch 2 times, most recently from 25a5894 to 55031b7 Compare June 20, 2026 06:04
@IvanaGyro
IvanaGyro force-pushed the claude/pybind11-stubgen-stubs-ebny1c branch from bffc010 to c1e1f45 Compare June 20, 2026 06:12
@IvanaGyro
IvanaGyro force-pushed the claude/fix-stubtest-errors branch from 55031b7 to f182481 Compare June 20, 2026 06:16
@IvanaGyro
IvanaGyro marked this pull request as ready for review June 20, 2026 07:07
@IvanaGyro
IvanaGyro marked this pull request as draft June 20, 2026 07:07
@ianmccul

Copy link
Copy Markdown
Collaborator

Heads-up from running the full pytest pytests/ on the rebased stack — not caused by this PR, flagging for awareness:

pytests/example/TDVP/tdvp1_dense_test.py::test_tdvp1_dense fails its tolerance:

error = np.abs(Es[-1] - (-1.0 * Nsites)) = 1.72e-4
assert error < 1e-6

This is unrelated to the changes here: TDVP drives time evolution through linalg.Lanczos_Exp, not the ExpH/ExpM bindings this PR touches, and the test passed before rebasing onto current master. master reworked Lanczos_Exp since this branch's fork point (#889 "Fix TDVP Lanczos_Exp instability", #892 "Fix Lanczos_Exp warning at full Krylov dimension", and "Fix Lanczos_Exp one-dimensional Krylov spaces"), which governs this result. Worth tracking separately on master — in particular whether the 1e-6 tolerance is still appropriate after those changes.

— Claude

Generated by Claude Code

@claude: When examining failed tests, do not start by questioning the tolerance value. You should first inspect carefully the tested code. In this case, the TDVP test is a trivially local imaginary-time evolution problem that should, in principle, converge to essentially numerical precision. In fact the 1e-6 tolerance here is far too weak: for a long time it was allowing implementation bugs in Lanczos_Exp and related functions to go unnoticed. Almost certainly, it means the branch being tested is following a numerically different path or missing a relevant fix.

@ Humans: Recommend putting above advice into local CLAUDE.md or other AI-agent instructions.

I note that the PR branch has since been rebased onto current master.

In this case it is essentially a rediscovery of #888. After #889 and #892 that test passes with tolerance around 4.21e-8. And that is primarily due to the relatively short imaginary time evolution. So actually the tolerance is weaker than it needs to be, and it isn't yet a sharp test of the numerics.

@ianmccul

Copy link
Copy Markdown
Collaborator

If the goal is to bind linalg.ExpH / linalg.ExpM with one overload per Python-distinguishable dtype, the cleaner solution is to expose one C++ overload per dtype as well, most naturally through templated C++ entry points.

It does not make much sense to split the Python binding by dtype, immediately fold those overloads back into a runtime-selected Cytnx dtype in C++, and then dispatch again at runtime to the backend implementation. That keeps the fragile runtime dtype path in the middle of the call chain.

We have already seen with Exp / Expf how easy it is for these runtime dtype-dispatch paths to become type-unsafe: the binding/API surface appears dtype-specific, but the implementation still passes untyped storage through a dispatch table and relies on the selected runtime dtype matching the actual storage.

As a general design rule, if the Python binding needs one overload per dtype, then the C++ side should also have strongly typed overloads or template instantiations for those dtypes. The binding should dispatch into those typed C++ functions directly, rather than funneling back through a runtime dtype and an untyped backend dispatch path.

@IvanaGyro
IvanaGyro force-pushed the claude/pybind11-stubgen-stubs-ebny1c branch from 725a8dd to 37e3210 Compare June 21, 2026 17:22
@IvanaGyro
IvanaGyro force-pushed the claude/fix-stubtest-errors branch 3 times, most recently from 6832f72 to bab016c Compare June 21, 2026 19:02
@IvanaGyro
IvanaGyro force-pushed the claude/pybind11-stubgen-stubs-ebny1c branch from 37e3210 to 9e7e777 Compare June 21, 2026 19:33
@IvanaGyro
IvanaGyro force-pushed the claude/fix-stubtest-errors branch 2 times, most recently from 3088d86 to 247f104 Compare June 22, 2026 02:37
@IvanaGyro
IvanaGyro force-pushed the claude/fix-stubtest-errors branch from 1e9b842 to 262d3d3 Compare July 8, 2026 09:42
@codecov

codecov Bot commented Jul 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.74699% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.29%. Comparing base (f64b2f5) to head (2d32a29).
⚠️ Report is 10 commits behind head on master.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
pybind/linalg_py.cpp 84.50% 11 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #915      +/-   ##
==========================================
+ Coverage   72.11%   72.29%   +0.18%     
==========================================
  Files         225      226       +1     
  Lines       28207    27944     -263     
  Branches       71       71              
==========================================
- Hits        20341    20202     -139     
+ Misses       7845     7721     -124     
  Partials       21       21              
Flag Coverage Δ
cpp 72.40% <86.74%> (+0.18%) ⬆️
python 64.13% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
C++ backend 71.66% <ø> (+0.10%) ⬆️
Python bindings 76.69% <86.74%> (+0.89%) ⬆️
Python package 64.13% <ø> (ø)
Files with missing lines Coverage Δ
pybind/complex_arg.hpp 100.00% <100.00%> (ø)
pybind/linalg_py.cpp 82.41% <84.50%> (+7.67%) ⬆️

... and 5 files with indirect coverage changes


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update f64b2f5...2d32a29. Read the comment docs.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@IvanaGyro
IvanaGyro marked this pull request as ready for review July 9, 2026 02:18
@IvanaGyro
IvanaGyro requested review from pcchen and yingjerkao July 9, 2026 02:18
@IvanaGyro

Copy link
Copy Markdown
Member Author

@pcchen May you help to review this?

IvanaGyro and others added 3 commits July 13, 2026 17:21
Several bindings changed on master without regenerating the committed
cytnx/cytnx/*.pyi stubs, so they drifted from what the pinned
pybind11 3.0.4 / pybind11-stubgen 2.5.5 toolchain actually emits.
Regenerate them from a clean build so the committed stubs match the
current extension before layering further binding changes on top.

Co-Authored-By: Claude <noreply@anthropic.com>
linalg.ExpH and linalg.ExpM were bound once per C++ scalar dtype -- the native
cytnx_* types plus py::numpy_scalar<...> variants, roughly twenty overloads per
function across the UniTensor and Tensor signatures. pybind11-stubgen collapses
several distinct C++ types onto the same Python annotation, so the generated stub
carried duplicate @typing.overload signatures that mypy rejects with
overload-cannot-match.

Keep one binding per Python-distinguishable dtype and drop the redundant ones:

- A Python float/complex already binds to the cytnx_double/cytnx_complex128
  overloads, and np.float64/np.complex128 are builtin subclasses caught by those
  same overloads, so the cytnx_float, cytnx_complex64, numpy_scalar<double> and
  numpy_scalar<complex<double>> overloads were unreachable duplicates.
- The fixed-width integer overloads all accept the same Python int, so replace
  them with a single py::int_ overload that dispatches on magnitude to the int64
  or uint64 kernel. Python int is arbitrary precision, which no single fixed-width
  C++ overload can cover, so the runtime branch keeps the whole range behind one
  signature.

Under pybind11 3.0.4 the kept overloads render as the numpy.* scalar types,
`int` (py::int_), `typing.SupportsFloat | typing.SupportsIndex` (cytnx_double),
and `typing.SupportsComplex | typing.SupportsFloat | typing.SupportsIndex`
(cytnx_complex128). The complex128 annotation is a supertype of the cytnx_double
and integer ones, but it is registered last, so every earlier narrower overload
stays reachable and mypy reports no overload-cannot-match. A bind_exp_scalar
helper shares the registration across the four signatures.

Two `b` defaults are supplied through py::arg_v with an explicit repr so the
stub stays valid against the widened 3.0.4 annotations, rather than letting
pybind11 format the default itself:

- The numpy_scalar overloads use a `numpy.<type>(...)` repr. Otherwise pybind11
  formats the default from the numpy scalar's own repr -- `np.<type>(...)` under
  numpy 2.x -- an unimported `np` alias that mypy rejects; naming the imported
  `numpy` module lets stubgen evaluate it and emit an elided `...`.
- The cytnx_complex128 overload uses a literal `...` repr. pybind11 3.0.4
  annotates the parameter `SupportsComplex | SupportsFloat | SupportsIndex`,
  which does not include the builtin `complex` (typeshed's `complex` has no
  `__complex__`), so a `0j` default would be rejected as an incompatible
  default. A `numpy.complex128(...)` repr would also elide, but it would
  misdescribe the default since this overload accepts a Python `complex`, not a
  numpy scalar, so `...` is used to state "elided" without claiming a type.

The defaults still exist at runtime, so mypy.stubtest is unaffected, and
tools/generate_stubs.py needs no special-casing for these arguments.

numpy complex64 scalars are routed through the complex128 kernel because
cytnx::linalg::Exp returns incorrect values for complex64 tensors
(#914), on which both ExpH and ExpM depend; drop that reroute once
that is fixed.

Add a regression test that ExpH and ExpM accept Python and numpy scalars of every
supported dtype, individually and in mixed (a, b) pairs, and match a NumPy
eigendecomposition reference, with a dedicated complex64-matches-complex128 case.

Co-Authored-By: Claude <noreply@anthropic.com>
After rebasing onto current master (Axpy API removal #787f9d8c, rank-zero
support #1026, Exp/Expf consolidation #1047), the committed stubs no longer
matched what pybind11-stubgen 2.5.5 produces from the current bindings. The
textual rebase auto-merged the .pyi files but generated files must be
regenerated, not merged.

Regenerated all stubs from a fresh openblas-cpu build (pybind11 3.0.4,
pybind11-stubgen 2.5.5). Net effect over the rebased tree is Device.pyi and
__init__.pyi only (the drift from #1026/#1047); linalg.pyi already matched.
Regeneration is idempotent, and `mypy.stubtest cytnx.cytnx.linalg` reports no
ExpH/ExpM overload-cannot-match. linalg_exp_test.py: 26 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@yingjerkao
yingjerkao force-pushed the claude/fix-stubtest-errors branch from 262d3d3 to 82824e8 Compare July 13, 2026 09:27

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 82824e8134

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cytnx/cytnx/__init__.pyi
IvanaGyro and others added 2 commits July 14, 2026 14:20
…-errors

Co-Authored-By: Claude <noreply@anthropic.com>
Regenerated with Python 3.10 (the requires-python floor) per
CONTRIBUTING.md's stub regeneration recipe, after merging origin/master
into this branch. Two kinds of drift surface:

- The pybind11-stubgen run that produced the previous regeneration used
  a newer interpreter than the 3.10 floor, so `_from_numpy`'s parameter
  was annotated `collections.abc.Buffer` (added to collections.abc only
  in Python 3.12) instead of the 3.10-compatible `typing_extensions.Buffer`.
  Regenerating with 3.10 restores the compatible annotation and the
  `import typing_extensions` it depends on.
- The merged-in master history includes commits that changed pybind
  bindings without regenerating the committed stubs in the same commit
  (e.g. "refactor(pybind): bind in-place methods directly, drop the
  c__* shadow API (#779)", which removed Bond's clear_type/set_type/
  c_getDegeneracy_refarg/c_group_duplicates_refarg/c_redirect_ bindings
  and added UniTensor.combineBond/norm/set_label_/set_name_/tag_ and a
  Tensor.norm binding). This regeneration catches the committed stubs
  up to those bindings.

`python -m mypy.stubtest cytnx.cytnx` reports zero errors against
linalg.pyi; the remaining errors are entirely in __init__.pyi's
Tensor/UniTensor operator overloads, the pre-existing baseline this
PR's description already scopes out to a dedicated follow-up (#928).

Co-Authored-By: Claude <noreply@anthropic.com>
@IvanaGyro
IvanaGyro force-pushed the claude/fix-stubtest-errors branch from da83f1f to bbdd1c8 Compare July 14, 2026 14:21

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bbdd1c8a62

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cytnx/cytnx/linalg.pyi Outdated
IvanaGyro and others added 3 commits July 14, 2026 20:52
pybind11 3.0.4 renders a cytnx_complex128 parameter as
typing.SupportsComplex | typing.SupportsFloat | typing.SupportsIndex.
typeshed's builtin complex has no __complex__ (real-valued dunders
like __float__ don't satisfy SupportsComplex, which requires
__complex__ specifically), so SupportsComplex alone rejects a plain
Python complex literal under strict type checkers even though the
binding accepts it directly (e.g. cytnx.linalg.ExpH(t, 2+1j)). Add a
sanitize() substitution that prefixes the union with `complex |` so
the emitted annotation covers the full accepted range.

Co-Authored-By: Claude <noreply@anthropic.com>
Reflects the complex | prefix added to every SupportsComplex |
SupportsFloat | SupportsIndex union in the committed stubs, including
ExpH/ExpM's a/b parameters (cytnx.linalg.ExpH(t, 2+1j) and the mixed
ExpH(t, 2.0, 1+0j) case now type-check).

Co-Authored-By: Claude <noreply@anthropic.com>
Move the fix for ExpH/ExpM's stub-visible complex parameter type from
tools/generate_stubs.py's post-processing regex to the binding itself,
per review feedback that the generator script shouldn't need to know
about this.

pybind11's own type_caster<std::complex<T>> (pybind11/complex.h)
hardcodes the PARAMETER annotation as "typing.SupportsComplex |
typing.SupportsFloat | typing.SupportsIndex", omitting builtin
`complex` -- typeshed's `complex` only gained `__complex__` in Python
>= 3.11, so under this project's Python 3.10 floor a stub generated
from that caster rejects a plain complex literal (e.g. `ExpH(t,
2+1j)`) even though the caster's own load() accepts it directly via
PyComplex_AsCComplex. The RETURN annotation is already correctly
"complex"; only the parameter side is missing it. This is baked into
pybind11's own header, not something an ordinary .def() call in this
codebase's binding code controls.

Added pybind/complex_arg.hpp: a thin ComplexArg wrapper around
cytnx_complex128 with its own pybind11::detail::type_caster
specialization that delegates load()/cast() entirely to pybind11's own
complex caster (no behavior change) and declares the corrected
parameter annotation. bind_exp_scalar's cytnx_complex128 overload in
linalg_py.cpp now takes ComplexArg for `a`/`b` instead of raw
cytnx_complex128. Because the corrected annotation includes `complex`,
the `b` default can now use a real `0j` repr instead of the previous
elided `...` (pybind11 3.0.4's uncorrected annotation rejected a
literal `0j` default as incompatible).

Scoped to ExpH/ExpM only, the functions this PR's review comment
concerns -- not a global override of pybind11's own
type_caster<std::complex<T>>, which would affect every complex128/
complex64-typed parameter across the whole codebase. The
generator-side regex in tools/generate_stubs.py stays in place for
every other occurrence of this same annotation gap (Scalar, Storage,
Tensor, UniTensor, ...) until they migrate to ComplexArg too, tracked
separately; the regex's negative lookbehind is idempotent against text
this wrapper already produces correctly.

Verified: mypy.stubtest reports nothing for ExpH/ExpM specifically
(676 total errors, unchanged from before this commit -- all
pre-existing, unrelated __init__.pyi issues); pytest
pytests/linalg_exp_test.py (26 tests) passes; a direct runtime check
(ExpH(t, 2+1j)) confirms behavior is unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
IvanaGyro added a commit that referenced this pull request Jul 15, 2026
Two gaps left over from the earlier Scalar-reorder pass and #915's
keep-set consolidation, both still on Tensor:

- fill/append were still bound once per raw C++ dtype (complex128,
  complex64, double, float, int64, uint64, int32, uint32, int16,
  uint16, bool), duplicating the same overload-cannot-match problem
  #915/ecb1fbd already fixed for the arithmetic operators. Replaced
  with the same numpy_scalar/py::int_/double/complex128 keep-set.
- __iadd__, __isub__, __imul__, __itruediv__, __ifloordiv__, __ne__,
  and __setitem__ still had their Scalar overload registered after
  cytnx_double/cytnx_complex128, unreachable in the stub for the same
  structural-subtyping reason as the operators already fixed (Scalar
  defines __float__/__complex__, so it satisfies the SupportsFloat/
  SupportsComplex protocols those overloads render as). These use a
  py::object self + multi-line lambda body shape that the original
  scan (looking for `[]` immediately after the comma) didn't match, so
  they were missed. Reordered Scalar to register right after
  py::int_, before cytnx_double/cytnx_complex128, matching every other
  operator.

Co-Authored-By: Claude <noreply@anthropic.com>
@IvanaGyro

IvanaGyro commented Jul 16, 2026

Copy link
Copy Markdown
Member Author

I am tired on keeping this PR on tip of the master branch. I have maintained this stub change for weeks. During these weeks, many PRs merged into master, which cause me to update the branch and the following branch (#1053).

Please take over this PR and the following PRs. I am stopping my AI bot tracing on these PRs.

@yingjerkao

Copy link
Copy Markdown
Collaborator

I will merge this PR and close it

…-errors

# Conflicts:
#	cytnx/cytnx/__init__.pyi
@yingjerkao
yingjerkao merged commit 09bfcb9 into master Jul 17, 2026
13 checks passed
@yingjerkao
yingjerkao deleted the claude/fix-stubtest-errors branch July 17, 2026 06:12
yingjerkao added a commit that referenced this pull request Jul 17, 2026
Brings #1053 up to date with master after #915 landed. Only two files
conflicted:

- pybind/tensor_py.cpp: master (#941) deliberately UNBINDS Tensor's
  __floordiv__/__rfloordiv__/__ifloordiv__ (binding them to Div would make
  `t // x` silently perform true division). Master's own comment states this
  "supersedes the P1-T2/T3 keep-set treatment these operator groups
  received" -- i.e. it directly supersedes #1053's floordiv keep-set.
  Resolved by taking master's unbind; #1053's keep-set consolidation for all
  other operators (add/sub/mul/truediv/mod/eq, plus Storage from_pylist and
  UniTensor/Scalar/LinOp/Symmetry) is preserved. #1053's keep-set tests do
  not exercise floordiv, so nothing of #1053 breaks.

- cytnx/cytnx/__init__.pyi: regenerated from the merged bindings via
  tools/generate_stubs.py (the correct resolution for generated output),
  reverting only a machine-specific Device.pyi Ncpus value.

Testing (openblas-cpu): pycytnx builds; mypy.stubtest reports 0
overload-cannot-match (down from 535 pre-#1053), leaving only the pre-
existing 7 override + 5 assignment __eq__/__hash__ warnings (a separate,
out-of-scope class); pytest pytests/ = 308 passed, 1 skipped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
yingjerkao added a commit that referenced this pull request Jul 17, 2026
…aining

Fix remaining overload-cannot-match errors with the #915 keep-set strategy
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.

3 participants