refactor(utils)!: drop namespace-scope complex/builtin operator overloads (#1003)#1092
refactor(utils)!: drop namespace-scope complex/builtin operator overloads (#1003)#1092yingjerkao wants to merge 3 commits into
Conversation
…oads (#1003) Ian's fold-in from the #1003 review. utils/complex_arithmetic.hpp declared a large set of namespace-scope operators (+ - * / ==) between cytnx_complex64/128 and every builtin scalar. Since cytnx_complex64/128 are aliases for std::complex<float/double> and builtins convert into them, these leaked into ordinary overload resolution for unrelated code -- amplified by C++20's reversed operator== rewrite. The classic symptom: `std::vector<bool>::reference == bool` is ambiguous under `using namespace cytnx` (it picks up the reversed cytnx complex== candidates). The header is pulled in by the umbrella utils.hpp, so the operators were in scope codebase-wide (and for any downstream `using namespace cytnx`). Fix: delete complex_arithmetic.{hpp,cpp} and its three include sites. std::complex already provides complex<>-real arithmetic and comparison, so the only load-bearing cases were mixed complex128 x complex64 (which std::complex does not define). A CPU build with the header removed pinned the entire blast radius to three files: - Kron_internal.hpp / Outer_internal.cpp: compute `static_cast<TO>(a) * static_cast<TO>(b)` (the output storage type), matching the #1003 "compute through the operation's output type" rule; - Physics.cpp (spin 'y'): build the coefficient as a real and construct the imaginary entry directly, instead of `complex(0,1) * real`. The == overloads had zero call sites -- pure pollution. Adds tests/overload_hygiene_test.cpp: a compile-time guard (with runtime asserts) for the vector<bool>-reference-vs-bool case; it fails to build if the operators return. Testing (CPU, openblas): library + test_main build clean; OverloadHygiene passes (and fails on pre-fix master -- reproduced); an independent numeric check confirms Kron/Outer of ComplexDouble x ComplexFloat promote to ComplexDouble with exact mixed products and spin(1/2,'y') == [[0,-i/2],[i/2,0]]. GPU-config compile-check of utils_internal_interface.cpp and cuOuter_internal.cu passes (the deleted header's UNI_GPU cuComplex== block had no users). Advances #1003 (Ian's operator-hygiene fold-in). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
| y += cytnx_uint64(tmp2 % shape2[j]) * shape2_acc[j]; | ||
| } | ||
| out[i] = Lin[x] * Rin[y]; | ||
| out[i] = static_cast<TO>(Lin[x]) * static_cast<TO>(Rin[y]); |
There was a problem hiding this comment.
Casting both operands to the output type TO before multiplying them can be highly inefficient. For example, if both inputs are real (double) but the output is complex (cytnx_complex128), casting them first forces a complex multiplication (which requires 4 real multiplications and 2 additions) instead of a single real multiplication. Since Cytnx targets C++20, we can use if constexpr with a requires expression to check if the operands can be multiplied directly, falling back to casting only when mixed complex types are involved.
if constexpr (requires(TL l, TR r) { l * r; }) {
out[i] = static_cast<TO>(Lin[x] * Rin[y]);
} else {
out[i] = static_cast<TO>(Lin[x]) * static_cast<TO>(Rin[y]);
}|
|
||
| for (unsigned long long r = 0; r < j1 * j2; r++) { | ||
| _out[r] = _Lin[cytnx_uint64(r / j2)] * _Rin[(r % j2)]; | ||
| _out[r] = static_cast<TO>(_Lin[cytnx_uint64(r / j2)]) * static_cast<TO>(_Rin[(r % j2)]); |
There was a problem hiding this comment.
Casting both operands to the output type TO before multiplying them can be highly inefficient. For example, if both inputs are real but the output is complex, casting them first forces a complex multiplication instead of a single real multiplication. Since Cytnx targets C++20, we can use if constexpr with a requires expression to check if the operands can be multiplied directly, falling back to casting only when mixed complex types are involved.
if constexpr (requires(T1 l, T2 r) { l * r; }) {
_out[r] = static_cast<TO>(_Lin[cytnx_uint64(r / j2)] * _Rin[(r % j2)]);
} else {
_out[r] = static_cast<TO>(_Lin[cytnx_uint64(r / j2)]) * static_cast<TO>(_Rin[(r % j2)]);
}| Out.at<cytnx_complex128>({a, a - 1}) = | ||
| cytnx_complex128(0, 1) * pow((S + 1) * (2 * a) - (a + 1) * a, 0.5) / 2; | ||
| cytnx_complex128(0, cytnx_double(pow((S + 1) * (2 * a) - (a + 1) * a, 0.5)) / 2); | ||
| if (a != Dim - 1) | ||
| Out.at<cytnx_complex128>({a, a + 1}) = | ||
| cytnx_complex128(0, -1) * pow((S + 1) * (2 * a + 2) - (a + 2) * (a + 1), 0.5) / 2; | ||
| Out.at<cytnx_complex128>({a, a + 1}) = cytnx_complex128( | ||
| 0, -(cytnx_double(pow((S + 1) * (2 * a + 2) - (a + 2) * (a + 1), 0.5)) / 2)); |
There was a problem hiding this comment.
Using pow(..., 0.5) is less efficient and less readable than std::sqrt. Additionally, the explicit cast to cytnx_double is redundant because pow (and std::sqrt) already returns a double. According to the repository style guide, we should avoid propagating legacy cytnx_XXXX typedefs for local arithmetic types and prefer standard C++ types like double.
| Out.at<cytnx_complex128>({a, a - 1}) = | |
| cytnx_complex128(0, 1) * pow((S + 1) * (2 * a) - (a + 1) * a, 0.5) / 2; | |
| cytnx_complex128(0, cytnx_double(pow((S + 1) * (2 * a) - (a + 1) * a, 0.5)) / 2); | |
| if (a != Dim - 1) | |
| Out.at<cytnx_complex128>({a, a + 1}) = | |
| cytnx_complex128(0, -1) * pow((S + 1) * (2 * a + 2) - (a + 2) * (a + 1), 0.5) / 2; | |
| Out.at<cytnx_complex128>({a, a + 1}) = cytnx_complex128( | |
| 0, -(cytnx_double(pow((S + 1) * (2 * a + 2) - (a + 2) * (a + 1), 0.5)) / 2)); | |
| Out.at<cytnx_complex128>({a, a - 1}) = | |
| cytnx_complex128(0, std::sqrt((S + 1) * (2 * a) - (a + 1) * a) / 2); | |
| if (a != Dim - 1) | |
| Out.at<cytnx_complex128>({a, a + 1}) = | |
| cytnx_complex128(0, -std::sqrt((S + 1) * (2 * a + 2) - (a + 2) * (a + 1)) / 2); |
References
- For new or touched code, avoid propagating legacy cytnx_XXXX typedefs as ordinary loop counters, sizes, flags, or local arithmetic types. Prefer standard C++ types unless the value is specifically a dtype-backed tensor/storage scalar. (link)
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1092 +/- ##
===========================================
+ Coverage 57.98% 73.67% +15.69%
===========================================
Files 229 225 -4
Lines 33459 27771 -5688
Branches 71 71
===========================================
+ Hits 19401 20461 +1060
+ Misses 14036 7289 -6747
+ Partials 22 21 -1
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 52 files 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".
| @@ -19,8 +19,6 @@ | |||
| #include "vec_sort.hpp" | |||
| #include "dynamic_arg_resolver.hpp" | |||
There was a problem hiding this comment.
Preserve complex-scalar arithmetic
With this umbrella include removed, C++ callers that include cytnx.hpp/utils.hpp and use the public cytnx_complex64/128 aliases with integer or mismatched-precision scalars lose previously available arithmetic/comparison: for example cytnx_complex128 z; z + cytnx_int32{1} and z == 0 are not provided by std::complex because its non-member templates require the scalar to deduce as the same T. The change note says only mixed complex128 × complex64 loses support, but the deleted overloads also covered these common complex/builtin cases, so this is a broader source break for downstream C++ users.
Useful? React with 👍 / 👎.
|
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 |
Problem
Ian's fold-in from the #1003 review.
include/utils/complex_arithmetic.hppdeclared ~300 namespace-scope operators (+ - * / ==) betweencytnx_complex64/128and every builtin scalar. Becausecytnx_complex64/128are aliases forstd::complex<float/double>and builtins convert into them, these leak into ordinary overload resolution for unrelated code — amplified by C++20's reversedoperator==rewrite. The reported symptom:The header is pulled in by the umbrella
utils.hpp, so the operators were in scope codebase-wide (and for any downstreamusing namespace cytnx).Fix
Delete
complex_arithmetic.{hpp,cpp}and its three include sites.std::complexalready providescomplex<>–real arithmetic and comparison; the only load-bearing cases were mixedcomplex128 × complex64(whichstd::complexdoesn't define). A CPU build with the header removed pinned the entire blast radius to three files:Kron_internal.hpp/Outer_internal.cpp— computestatic_cast<TO>(a) * static_cast<TO>(b)(the output storage type), matching the Merge CUDA unary and binary elementwise operations through one typed kernel framework #1003 "compute through the operation's output type" rule.Physics.cpp(spin 'y') — build the coefficient as a real and construct the imaginary entry directly instead ofcomplex(0,1) * real.The
==overloads had zero call sites — pure pollution.Adds
tests/overload_hygiene_test.cpp: a compile-time guard (with runtime asserts) for thevector<bool>-reference-vs-boolcase; it stops compiling if the operators return.Testing
CPU (openblas): library +
test_mainbuild clean;OverloadHygienepasses (and fails to compile on pre-fixmaster— reproduced). An independent numeric harness confirmsKron/OuterofComplexDouble × ComplexFloatpromote toComplexDoublewith exact mixed products, andspin(½,'y') == [[0,-i/2],[i/2,0]]. GPU-config compile-check ofutils_internal_interface.cppandcuOuter_internal.cupasses (the deleted header'sUNI_GPUcuComplex==block had no users).Note
Marked
!because it removes public namespace-scope operators. In practicestd::complexcoverscomplex<>–real use; only mixedcomplex128 × complex64(rare in downstream code) loses an implicit operator. Independent of the GPU-side #1003 work in the sibling branch.Advances #1003 (Ian's operator-hygiene fold-in).
🤖 Generated with Claude Code