refactor(utils)!: drop namespace-scope complex/builtin operator overloads (#1003) - #1092
Conversation
There was a problem hiding this comment.
Code Review
This pull request removes the namespace-scope complex-to-builtin operator overloads in utils/complex_arithmetic.hpp and utils/complex_arithmetic.cpp to prevent them from leaking into ordinary overload resolution (resolving issue #1003). It also introduces a regression test overload_hygiene_test.cpp to guard against this. To support this removal, several files were updated to avoid relying on these operators, such as explicitly constructing complex numbers in src/Physics.cpp and casting operands in Kron_internal.hpp and Outer_internal.cpp.
Feedback on the changes:
- In
Kron_internal.hppandOuter_internal.cpp, casting both operands to the output typeTObefore multiplication can be inefficient (e.g., forcing complex multiplication on real inputs). It is recommended to use C++20if constexprwith arequiresclause to multiply directly when possible. - In
src/Physics.cpp, usingpow(..., 0.5)and explicitcytnx_doublecasts should be replaced withstd::sqrtand standarddoubletypes to improve efficiency and align with the repository style guide.
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.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1092 +/- ##
==========================================
+ Coverage 73.06% 74.05% +0.99%
==========================================
Files 224 224
Lines 27642 27274 -368
Branches 71 71
==========================================
+ Hits 20196 20198 +2
+ Misses 7425 7055 -370
Partials 21 21
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 1 file with indirect coverage changes Continue to review full report in Codecov by Harness.
🚀 New features to boost your workflow:
|
…#1003) Review follow-up (#1092): - Kron_internal / Outer_internal: multiply the operands directly when that is well-formed (casting only the result) and fall back to casting each operand only for the mixed complex/real pairs where the direct product is ill-formed. Casting both up front forced a complex multiply for real inputs whose output type happens to be complex (Gemini). - Physics::spin: use std::sqrt instead of pow(..., 0.5) and drop the redundant cytnx_double cast (Gemini). - overload_hygiene_test: rename the test cases to PascalCase without underscores (IvanaGyro). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…#1003) Review follow-up (#1092): - Kron_internal / Outer_internal: multiply the operands directly when their native product casts to TO (e.g. real*real -> real, then widened to a complex TO), avoiding a needless complex multiply for real inputs; fall back to casting each operand to TO for the mixed complex/real pairs whose native product is a cytnx::Scalar (not convertible to std::complex) or is ill-formed. The `static_cast<TO>` inside the `requires` is essential: Scalar's implicit conversions make a bare `l * r` well-formed for those pairs even though the result cannot become TO, so `requires{ l * r }` alone would wrongly take the direct path and fail to compile (Gemini review, adapted for Cytnx's Scalar). - Physics::spin: use std::sqrt instead of pow(..., 0.5) and drop the redundant cytnx_double cast (Gemini). - overload_hygiene_test: rename the test cases to PascalCase without underscores (IvanaGyro). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ac570b1 to
bc367b3
Compare
|
Thanks — all addressed in the latest commit:
|
…review) Addresses the review of #1092: the Sx spin-operator matrix elements used pow(expr, 0.5) while the sibling Sy elements already use std::sqrt(expr). Switch to std::sqrt for consistency, readability, and to avoid the slower general pow path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Review status (bot threads):
These threads look ready to resolve. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 41070f4b6e
ℹ️ 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".
|
Codex review notes: Deleting the 946-line handwritten overload layer is directionally good, but I think this still needs changes before merge.
cytnx_complex128 z;
z * 2; // int
z == 0; // int
z + cytnx_float{1}; // float
cytnx_complex64 w;
w * cytnx_double{2}; // doubleIt is not limited to
if constexpr (requires { static_cast<TO>(l * r); })
out[i] = static_cast<TO>(Lin[x]) * static_cast<TO>(Rin[y]);
After rebasing, the clean version should be considerably smaller: remove the old implementation and include sites, update the Makefile, use explicit output-type arithmetic in |
…overloads (#1003) `include/utils/complex_arithmetic.hpp` declared ~300 namespace-scope operators (`+ - * / ==`) between `cytnx_complex64/128` and every builtin scalar, with the implementations in a 638-line `.cpp`. Because `cytnx_complex64/128` are aliases for `std::complex<float/double>` and builtins convert into them, those declarations entered ordinary overload resolution for unrelated code under `using namespace cytnx` -- amplified by C++20's reversed `operator==` rewrite. The reported symptom was that `std::vector<bool>::reference == bool` became ambiguous. Replace the whole surface with five constrained templates. `ComplexMixedOperands` admits a pair only when both operands are cytnx dtypes, at least one is a complex dtype, and `std::complex` does not already provide the operator. Unrelated types can no longer form a candidate, while `z * 2`, `z == 0`, `z + cytnx_float{1}` and `complex64 * double` keep working. BREAKING CHANGE: mixed complex/real results now follow `Type.type_promote`. The retired overloads returned `complex64` for `complex64 op double`, discarding the double's precision; that pair now yields `complex128`. The `UNI_GPU` `cuDoubleComplex`/`cuFloatComplex` `operator==` declarations are dropped as well (they had no call sites; #1019 owns the CUDA-side migration). Also: - `Kron_general` converts both operands to the output type `TO` before multiplying, per the #1003 "compute through the operation's output type" rule, instead of relying on whichever operator C++ finds for the raw operand pair. - `physics::spin` uses `std::sqrt` instead of `pow(x, 0.5)` and builds the 'y' component's imaginary entry directly. - `developer_tools/Makefile` no longer references the deleted `complex_arithmetic` object. Tests: `tests/overload_hygiene_test.cpp` (pollution guard + the mixed complex/scalar policy: result dtypes and values), `tests/Physics_test.cpp` (spin-1/2 and spin-1 matrices against textbook values, plus [Sx,Sy] = i Sz), and `tests/linalg_test/Kron_test.cpp` (mixed-precision and signed/unsigned Kron). The hygiene guard is verified to fail to compile on pre-fix master. Advances #1003. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
41070f4 to
83d6120
Compare
|
@ianmccul — thanks, all five points are addressed. The branch is rebased onto current 1. The API break — took the constrained-template option. You were right that the break was understated: template <class L, class R>
concept ComplexMixedOperands = CytnxType<L> && CytnxType<R> &&
(is_complex_floating_point_v<L> || is_complex_floating_point_v<R>) &&
!internal::std_complex_handles_v<L, R>;Three conditions carry it: There is still a breaking change, but it is a promotion change rather than a removal, and I The only outright removals are the 2. The
out[i] = static_cast<TO>(Lin[x]) * static_cast<TO>(Rin[y]);One tradeoff worth recording: this does cost a real multiply pair on 3. Removed 4. Rebased. On current 5. Tests — the claims are committed now, not just asserted.
One correction to the earlier claim on this PR: the previous version of the hygiene guard did Local gates on this rebase:
On the GPU suite: two tests abort with Bot threads, for the record:
|
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
|
To use Codex here, create an environment for this repo. |
|
Follow-up on the GPU suite, as promised — it finished, and nothing in it is caused by this PR. Three tests fail, all reproduced on
Everything else passes (a handful of The third one is arguably a test bug rather than an environment problem: Environment: RTX 4070 Ti SUPER (sm_89), CUDA 13.0, |
…ge (#1003) The four unary GPU dispatchers added earlier in this PR each carried their own copy of the linear kernel, the launch configuration, a typed launch helper and an eleven-case dtype switch. Collapse all of that into cuUnaryDispatch.cuh: one `unary_kernel` + `launch`, with the operations expressed as AbsOp, ExpOp, PowOp{p} and ConjOp, and the per-operation dtype/output rules as traits. An in-place operation passes the same buffer as input and output. Dispatch now runs over the ordinary Cytnx value types through as_storage_variant()/storage_cast<T>, with to_cuda_t applied only at the kernel-launch boundary, matching cuArithmeticDispatch.cuh (#1013). This drops the reinterpret_cast on the type-erased Storage_base::data(), which Storage.hpp says new code must not add callers of. Two consequences worth noting: - The Abs output rule is now internal::complex_value_type_t (#1092) instead of a local AbsOutput. That trait is specialized on std::complex, so it only applies once the rule is stated on the Cytnx value type, before to_cuda_t. - The hand-written cy_typeid_gpu_v check on `out` is gone: storage_cast<TOut> is now the single point that enforces the output dtype, so the invariant is checked rather than assumed. Each op functor returns exactly the operation's output type, so the store in unary_kernel is a same-type assignment and a TOut/TIn mismatch is a compile error instead of being absorbed by a blanket static_cast. With one shared complex-capable dispatch available, the legacy CUDA-C cuConj_inplace kernel is no longer reachable: cuSvd_internal.cu and cuGeSvd_internal.cu were its last callers, outside the lookup table this PR already removed, and their signature is exactly cuConj_inplace_dispatch's. Point them at it and delete cuConj_inplace_internal.cu, keeping only the dispatch declaration in the header. Also addresses the review style points: no leading-underscore locals, the block count is a plain `unsigned int` with the narrowing made explicit, and the error format strings use a trailing \n rather than the %s + "\n" pair. Numerical behavior is unchanged. In particular PowOp keeps the existing branches: complex<float> computes through complex<double> to preserve the double exponent, while the real float path narrows the exponent to match the CPU powf(float, float) counterpart. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Problem
include/utils/complex_arithmetic.hppdeclared ~300 namespace-scope operators (+ - * / ==)between
cytnx_complex64/128and every builtin scalar, implemented in a 638-line.cpp.Because
cytnx_complex64/128are aliases forstd::complex<float/double>and builtins convertinto them, these are effectively Cytnx operators over standard-library and builtin types: under
using namespace cytnxthey enter ordinary unqualified lookup for completely unrelated code,amplified by C++20's reversed
operator==rewrite. The reported symptom (#1003):The header is pulled in by the umbrella
utils.hpp, so the operators were in scopecodebase-wide and for any downstream
using namespace cytnx.Fix
Replace the whole hand-written surface with five constrained templates in the same header
(the
.cppis deleted). This is the alternative @ianmccul raised in review — tighten thedeclarations rather than delete the functionality:
Three conditions do the work:
CytnxType<L> && CytnxType<R>— both operands must be members ofType_list. This is whatkeeps
std::vector<bool>::reference, user-defined classes,char,long double, … from everforming a candidate. It also guarantees
type_promote_tis well-formed, so the constraint ischecked before any hard error can escape.
!std_complex_handles_v— pairsstd::complexalready provides an operator for(
complex<T>withcomplex<T>or withT) stay withstd, so the cytnx template can neverbecome a second equally-good candidate and make them ambiguous.
One
operator==suffices: C++20 synthesizesscalar == complexfrom the reversed candidate and!=from the negation.Also in this PR:
Kron_generalnow computesstatic_cast<TO>(Lin[x]) * static_cast<TO>(Rin[y])—explicit output-type arithmetic per the Merge CUDA unary and binary elementwise operations through one typed kernel framework #1003 rule, rather than relying on whichever operator
C++ finds for the raw operand pair. (The earlier
if constexpr (requires …)variant is gone;see the review thread.)
physics::spinusesstd::sqrtinstead ofpow(x, 0.5)and builds the'y'component'simaginary entry directly.
developer_tools/Makefileno longer references the deletedcomplex_arithmeticobject.Breaking change
The
!is for a type-promotion change, not a removal. Mixed complex/real results now foldthrough
Type.type_promote, which crosses the real/complex boundary by precision (#858, #982):complex64 * doublecomplex64(precision silently dropped)complex128complex64 + doublecomplex64complex128complex128 * complex64complex128complex128(unchanged)complex128 * intcomplex128complex128(unchanged)Everything Codex and @ianmccul listed as a would-be source break still compiles:
z * 2,z == 0,z + cytnx_float{1},w * cytnx_double{2}.The only removals are the
UNI_GPUcuDoubleComplex/cuFloatComplexoperator==declarations,which had no call sites (#1019 owns the CUDA-side migration).
Testing
tests/overload_hygiene_test.cpp— thestd::vector<bool>::reference == boolguard (acompile-time guard; the vector is deliberately non-const, since a const
vector<bool>yields aplain
booland would guard nothing), the concept's admit/reject set, and the mixedcomplex/scalar policy: result dtypes via
static_assertplus exact values for+ - * / ==including negative, fractional, unsigned,
booland cross-precision operands.Verified to fail to compile on pre-fix
master(5ambiguous overload for 'operator=='errors, GCC 13.3, C++20).
tests/Physics_test.cpp— spin-1/2 and spin-1x/y/zmatrices against the textbookħ = 1 values, plus an independent algebraic check that
[Sx, Sy] = i Szfor S = ½, 1, 3/2, 2,and the rejection of non-half-integer S.
tests/linalg_test/Kron_test.cpp— mixed-precisionComplexDouble × ComplexFloat,ComplexFloat × Double(the promotion that crosses the real/complex boundary), complex ×signed integer,
Uint32 × Int64, and a same-dtype control, all against hand-computed values.Gates run locally on this rebase:
debug-openblas-cpu(Debug + ASan): 1836/1836 ctest tests pass.pytest pytests/: 255 passed, 1 skipped.pycytnx) build and import;cytnx.physics.spin(0.5, 'y')is correct.USE_CUDA=ON) build of the library andtest_main.pre-commit runclean (clang-format v14).Advances #1003 (Ian's operator-hygiene fold-in).
🤖 Generated with Claude Code