fix(Tensor & UniTensor): support rank-zero tensors and zero extents - #1026
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces comprehensive support for rank-0 (scalar) Tensors and UniTensors across Cytnx, updating initialization, reshaping, indexing, serialization, and element-wise binary arithmetic operators to handle empty shapes. The review feedback highlights a critical safety issue in Cpr.cpp where bypassing the contiguity check for scalar broadcasting could lead to silent numerical corruption on non-contiguous tensors. Additionally, an improvement was suggested in Tensor.hpp to replace raw memcpy with iterator-based vector initialization for safer and cleaner code.
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.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1781bb972f
ℹ️ 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".
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1026 +/- ##
==========================================
+ Coverage 55.63% 57.29% +1.65%
==========================================
Files 228 229 +1
Lines 32896 33337 +441
Branches 71 71
==========================================
+ Hits 18303 19100 +797
+ Misses 14573 14216 -357
- Partials 20 21 +1
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 11 files with indirect coverage changes Continue to review full report in Codecov by Harness.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5b35c5cb10
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 321f6b7d2c
ℹ️ 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".
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces comprehensive support for rank-0 (scalar) Tensors and UniTensors in Cytnx, enabling proper scalar indexing, broadcast arithmetic, reductions, and trace operations, alongside an updated versioned serialization format. The review feedback identifies important safety and robustness improvements, specifically recommending checks for uninitialized tensors in element access methods to prevent undefined behavior, and adding an explicit header inclusion in tests to avoid potential compilation failures.
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.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 210bfdc818
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d7cb323c3a
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 93e9f1c45a
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9f07c50d0b
ℹ️ 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".
| inline Tensor HostScalarForGpuBroadcast(const Tensor &tensor, const int op_device) { | ||
| if (op_device != Device.cpu && tensor.is_scalar() && tensor.device() != Device.cpu) { | ||
| return tensor.to(Device.cpu); |
There was a problem hiding this comment.
Apply GPU scalar host copies to typed scalar overloads
Fresh evidence beyond the earlier Tensor/Tensor CUDA scalar fix is that this helper is only used by the Tensor/Tensor arithmetic paths; typed scalar overloads such as scalar_cuda + 1.0, 1.0 + scalar_cuda, and the analogous Sub/Mul/Div/Mod/Cpr overloads still pass the rank-0 CUDA Tensor storage directly to the CUDA size-1 branches. Those branches host-read *_in[0] before launching, so a newly supported rank-0 CUDA Tensor combined with a C++/Python numeric scalar can still read the device-resident managed value without the host-copy/synchronizing path this helper provides.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
This is long-standing behavior. Size-1 operands are passed by value to kernels using _Lin[0] / _Rin[0] on the host side before launch. If that storage is actually on GPU, this relies on managed-memory host access and synchronization behavior. It is terrible thing to do, but beyond the scope of this PR. It should get fixed by #1003 .
9f07c50 to
f6f6f5c
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 092b7d70b6
ℹ️ 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 |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces comprehensive support for rank-0 scalar Tensors and UniTensors across the codebase, updating backend implementations, linear algebra operations, and Python bindings to correctly handle scalar structures instead of treating them as 1-element rank-1 tensors. The review feedback highlights several violations of the repository style guide, specifically pointing out that newly introduced helper functions and utilities should be renamed from CamelCase to snake_case.
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.
|
Codex Review: Didn't find any major issues. Bravo. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
|
According to the repository style guide (line 48), new identifiers should use snake_case instead of CamelCase. All cases identified by Gemini have been resolved. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: db35534582
ℹ️ 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".
| if (!Lidx.empty()) { | ||
| memcpy(Lidx.data(), tmp->_inner_to_outer_idx[b].data(), | ||
| sizeof(cytnx_uint64) * this->_bonds.size()); | ||
| } |
There was a problem hiding this comment.
Use located sign indices for scalar fermionic contracts
When a rank-0 BlockFermionicUniTensor is used as the left factor in an outer-product contraction with a non-scalar fermionic tensor, this new empty-Lidx path lets the loop run once per output/RHS block, but the sign update below still indexes this->_signflip[b]. The scalar left operand has only one sign entry, so any RHS with more than one block reads past the scalar's sign vector instead of reusing IDL[0]/IDR[0], making valid scalar-factor contractions crash or corrupt the result.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
This is pre-existing, will open a separate issue
|
@codex review |
There was a problem hiding this comment.
Code Review
This pull request introduces comprehensive support for rank-0 (scalar) Tensors and UniTensors across the Cytnx library, updating C++ implementations, Python bindings, and serialization formats. Key mathematical operations, reductions, and element-wise arithmetic have been updated to return or broadcast rank-0 tensors correctly instead of 1-element rank-1 tensors. Additionally, Block and BlockFermionic UniTensors now properly preserve symmetry metadata for rank-0 blocks. The review feedback highlights a performance optimization opportunity in the contraction loops of both BlockFermionicUniTensor and BlockUniTensor, where pre-allocating the index vectors outside the loop can prevent repeated dynamic memory allocations in a hot path.
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.
|
I think we're close to final version now. |
There was a problem hiding this comment.
Code Review
This pull request introduces comprehensive support for rank-0 (scalar) Tensors and UniTensors across Dense, Block, and BlockFermionic implementations, updating various linear algebra operations and adding extensive unit tests. The review feedback highlights critical out-of-bounds read risks in Tensor_impl.cpp due to empty locator initialization, which should be corrected to {{0}}. Additionally, the feedback suggests performing is_void() checks before is_scalar() in Kron.cpp, using the is_void() and rank() helper methods for better consistency, accumulating loop results locally in Norm.cpp to avoid proxy overhead, replacing C-style casts with reinterpret_cast to adhere to the style guide, and resolving narrowing conversion warnings in iota_view usages.
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.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 48f686493e
ℹ️ 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 |
There was a problem hiding this comment.
Code Review
This pull request implements comprehensive support for rank-0 scalar Tensors and UniTensors across the Cytnx library. It introduces is_scalar() and is_void() predicates, allows empty shapes {} for rank-0 representations, and updates core linear algebra operations (such as Sum, Min, Max, Norm, Det, Vectordot, and Trace) to return rank-0 scalar tensors instead of rank-1 tensors of size 1. Additionally, it updates binary arithmetic operations, in-place operators, and element access/assignment to support rank-0 scalar broadcasting and empty-tuple () indexing in both C++ and Python bindings. File serialization has also been updated with a new versioned magic number to correctly preserve rank-0 structures. There are no review comments provided, so we have no further feedback to offer.
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.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1b16d4fae7
ℹ️ 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".
Remove the C++ scalar-size zeros/ones overloads so {} consistently denotes a rank-zero scalar shape and {N} denotes a rank-one tensor. Keep Python integer convenience in the bindings and clean up tests to use dtype/device-aware generators directly.
Co-Authored-By: Codex <codex@openai.com>
Co-Authored-By: Codex <codex@openai.com>
Co-Authored-By: Codex <codex@openai.com>
Finish the review-driven rank-zero hardening, zero-extent support, singleton Tensor broadcasting, documentation, stubs, and regression coverage. Co-Authored-By: Codex <noreply@openai.com>
84c21cd to
6375602
Compare
|
Latest version addressess all review items |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6375602e39
ℹ️ 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".
|
currently surveying the codebase for more zero-extent edge cases. |
|
will fix the extent-0 edge cases in a separate PR. this needs to get merged. |
BlockUniTensorTest.gpu_Trace read the rank-0 scalar from UT_diag.Trace(0, 1)
via at({0}), which #1026 (rank-0 UniTensor semantics) now rejects with
"rank-0 scalar expects no locator indices". The CPU Trace test was updated to
at({}) in #1026; the GPU test was missed because GPU tests don't run in CI.
Mirror the CPU fix. Surfaced by this PR's new GPU CI on its first run.
Co-Authored-By: Claude Opus 4.8 (1M context) <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>
Brings in #1039, #1047 (39 commits). Clean textual merge, but #1026's new test `UniTensor_baseTest.rank_detects_label_bond_mismatch` desyncs bonds/labels via `bonds().pop_back()` -- which no longer compiles now that this PR makes the mutable `bonds()` accessor const (immutable Bond, #846). That desync is unreachable through the public API by design, so the test now pokes the internal `_bonds` vector (const_cast) to still exercise rank()'s defensive size-mismatch guard. Verified: openblas-cpu + RUN_TESTS build clean (CI's editable-install compile gate reproduced locally); UniTensor_base/BlockFermionic/Bond gtests 38 passed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ue-division (#941) Reimplements the deferred half of #1015 on current master, which had since refactored the same six arithmetic files for rank-zero support (#1026), zero-extent (#1043), and type_promote (#984). - Convert CPU out-of-place AND in-place Add/Sub/Mul/Div/Cpr/Mod to typed std::visit over as_storage_variant() with storage_cast<TO> and typed *InternalImpl<TO,TL,TR> kernels; the dead legacy dispatch tables are removed. - True division (#941): `/` and `/=` yield a floating result (int/int -> Double) via make_floating_point_dtype; the rank-zero / broadcast output still routes through #1026's init_broadcast_binary_output (seeded with the floating dtype). - Mixed-dtype in-place TENSOR ops promote the LHS via storage_as_type_or_replace (no more truncation into the narrower LHS storage -- #941's core bug). - Weak-scalar (#980, per #1015's binding ruling): `tensor op= python-scalar` (a rank-0 RHS via scalar_as_rank0_tensor) PRESERVES the LHS dtype for Add/Sub/Mul and follows true-division for Div (Int64 /= 2.0 -> Double, Float /= 3.0 -> Float), keyed on Rt.rank()==0 in DispatchInplaceArithmeticCPU; complex-into-real still throws. - floordiv conforms to #1049 (scalar `ut // 2.0` kept, `ut1 // ut2` raises); #1015's original unbind-all is dropped. - normalize_() now promotes an integer block to Double under true division; its stale "stays Int32" comment is updated. Mod keeps an eager is_complex guard so a zero-extent complex Mod still throws (ZeroExtentLinalgTest). CPU only; GPU stays on the legacy dtype tables behind the unchanged public API (#1013 tracks the GPU conversion). Testing: full openblas-cpu (non-ASan) test_main 1784 passed / 11 skipped / 0 failed -- MixedDtypeArithmetic regressions, weak-scalar Tensor.ScalarInplace*, ZeroExtentLinalgTest, and DenseUniTensorTest true-division/normalize all green; clang-format v14 clean. Reimplements ff30d00, deferred from #1015 (#941). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ue-division (#941) Reimplements the deferred half of #1015 on current master, which had since refactored the same six arithmetic files for rank-zero support (#1026), zero-extent (#1043), and type_promote (#984). - Convert CPU out-of-place AND in-place Add/Sub/Mul/Div/Cpr/Mod to typed std::visit over as_storage_variant() with storage_cast<TO> and typed *InternalImpl<TO,TL,TR> kernels; the dead legacy dispatch tables are removed. - True division (#941): `/` and `/=` yield a floating result (int/int -> Double) via make_floating_point_dtype; the rank-zero / broadcast output still routes through #1026's init_broadcast_binary_output (seeded with the floating dtype). - Mixed-dtype in-place TENSOR ops promote the LHS via storage_as_type_or_replace (no more truncation into the narrower LHS storage -- #941's core bug). - Weak-scalar (#980, per #1015's binding ruling): `tensor op= python-scalar` (a rank-0 RHS via scalar_as_rank0_tensor) PRESERVES the LHS dtype for Add/Sub/Mul and follows true-division for Div (Int64 /= 2.0 -> Double, Float /= 3.0 -> Float), keyed on Rt.rank()==0 in DispatchInplaceArithmeticCPU; complex-into-real still throws. - floordiv conforms to #1049 (scalar `ut // 2.0` kept, `ut1 // ut2` raises); #1015's original unbind-all is dropped. - normalize_() now promotes an integer block to Double under true division; its stale "stays Int32" comment is updated. Mod keeps an eager is_complex guard so a zero-extent complex Mod still throws (ZeroExtentLinalgTest). CPU only; GPU stays on the legacy dtype tables behind the unchanged public API (#1013 tracks the GPU conversion). Testing: full openblas-cpu (non-ASan) test_main 1784 passed / 11 skipped / 0 failed -- MixedDtypeArithmetic regressions, weak-scalar Tensor.ScalarInplace*, ZeroExtentLinalgTest, and DenseUniTensorTest true-division/normalize all green; clang-format v14 clean. Reimplements ff30d00, deferred from #1015 (#941). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
BlockUniTensorTest.gpu_Trace read the rank-0 scalar from UT_diag.Trace(0, 1)
via at({0}), which #1026 (rank-0 UniTensor semantics) now rejects with
"rank-0 scalar expects no locator indices". The CPU Trace test was updated to
at({}) in #1026; the GPU test was missed because GPU tests don't run in CI.
Mirror the CPU fix. Surfaced by this PR's new GPU CI on its first run.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
BlockUniTensorTest.gpu_Trace read the rank-0 scalar from UT_diag.Trace(0, 1)
via at({0}), which #1026 (rank-0 UniTensor semantics) now rejects with
"rank-0 scalar expects no locator indices". The CPU Trace test was updated to
at({}) in #1026; the GPU test was missed because GPU tests don't run in CI.
Mirror the CPU fix. Surfaced by this PR's new GPU CI on its first run.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
This PR adds proper rank-0
TensorandUniTensor. Resolves #1016. It also adds proper support for tensors that have extent zero (such as an empty vector), following NumPy and PyTorch, and basically every other tensor library out there. Resolves #1040.Historically Cytnx represented several scalar results as rank-1 tensors with shape
{1}. That makes shape metadata ambiguous: a one-leg length-1 tensor is not the same object as a scalar. This PR starts separating those cases by treating an empty shape()as the scalar tensor shape, while preserving ordinary shape{1}as a rank-1 tensor.This adds more code than I was expecting, but of the net lines added, +942 are tests/pytests, and the rest is hardening, replacing the old rank-1-size-1 scalar hacks with explicit rank-0 behavior, and about about 93 added
cytnx_error_msgchecks, 55 added tests, and hardened many existing tests.The biggest user-visible change in Python is that contractions over all legs of a tensor (including inner products etc) now return a proper rank-0 tensor, so e.g. the example DMRG code needs
I think that's an improvement: previously the energy was a rank-1 tensor.
Also added
TensorandUniTensorhelpers.is_void(),.is_scalar(), and.is_empty(). The old model was not able to distinguish properly genuinely empty tensors from rank-0 tensors whereshapeis empty, and was not able to distinguish a rank-1 size-1 tensor from a genuine scalar.UniTensorhad no canonical way to store its symmetry data. Functions that needed the symmetry data would typically get it from_bonds[0].syms(), which of course doesn't exist for a rank-0UniTensor.The symmetry data is now held byUniTensoritself. To avoid too much code churn, existing behavior of setting the symmetry data from_bonds[0].syms()is still supported as a fallback, but retrieving the symmetry data should be done viathis->syms().This should be regarded as a breaking change for Tensor file I/O. Previous Cytnx cannot distinguish a genuine rank-1 size-1 tensor from a scalar-like tensor that should have been rank 0. Loading old Tensor files remains supported, but old shape
{1}is preserved as rank-1 rather than auto-converted to rank 0. Tensors saved in the new version are not loadable by old Cytnx because old Cytnx would choke on a genuine rank-0 Tensor. The Tensor file magic number has changed so new Tensor files loaded by old Cytnx will fail early.The SVD-style return values now follow a consistent rank-zero convention.
Svd/Gesvdon Tensor returnTensorobjects, andSvd/GesvdonUniTensorreturnUniTensorobjects: the singular valuesSremain a diagonal object in the same API family asUandvT,with block/symmetric SVD preserving the singular-value bond structure. Truncated SVD routines keep the existing homogeneous vector return type, so the optionalreturn_errobject is also aTensorforTensorAPIs and aUniTensorforUniTensorAPIs.return_err == 0appends nothing.return_err == 1appends a scalar rank-0 object containing the largest actually discarded singular value, or scalar zero if nothing was discarded. (I think this is a little inconsistent in the sense that it does not distinguish the case of discarding nothing at all vs discarding actual zero singular values. I'd favor returning some sentinel like -1 in the case where truely nothing was discarded.)return_err > 1appends a rank-1 vector containing all actually discarded singular values in descending order; if nothing was discarded, this is a tensor with shape {0}. For block/symmetric truncation, “discarded” means after the real truncation decision, including exact-degeneracy enlargement andmin_blockdim, so the error return reports values that were actually removed.The core API now has a clean distinction:
is_void()means uninitialized,is_scalar()means rank zero with one value, andis_empty()means initialized with logicalsize() == 0(i.e. it has rank > 0 but at least one extent is zero).Changes
Tensorto be initialized with an empty shape, allocating one storage element.Tensor::is_scalar(), true for initialized rank-0 tensors only.Tensor::is_void()and expose it to Python, replacing uses ofshape.empty()as an initialized/non-void proxy.item(),at({}),get({}), andset({}, ...)..item()as a size-one element extractor, NumPy-style: shape(),{1}, and{1,1}all work if storage size is one.Tensor::get(...)return a rank-0 Tensor rather than shape{1}.Tensor.is_scalar()andTensor.is_void()to Python, also theUniTensorversions.len(tensor)raise for rank-0 tensors, matching NumPy rank-0 array behavior.tensor[()]and denseunitensor[()].Tensor: shape{},{1},{1,1}, and other one-element shapes can broadcast in binary arithmetic and assignment.Tensor::is_scalar()remains a structural query that is true only for rank 0.UniTensorbroadcasting remains restricted to true rank-0 scalars because singleton bonds can carry symmetry information.Scalaroverload of Add, Sub, Mul, Div, and Mod, in both operand orders, so Void inputs fail with a controlled operation-level error.UniTensoruse a rank-0 backing block.Norm()andVectordot()return rank-0 tensors.Norm()return rank-0 Tensor results as well.Trace()toDenseUniTensor.syms().UniTensor. If symmetry data is not supplied, it is inferred from the first addedBond.BlockUniTensor::Trace_andBlockFermionicUniTensor::Trace_: after tracing the last two bonds, the old code could leave_bondsempty whilesyms()still indexed_bonds[0].Sortreturn rank-0 scalar tensors unchanged before reading the last shape dimension.Matmul_dgreject rank-0 operands before reading matrix dimensions fromshape().back()/shape()[0].Tensor::appendto distinguish void tensors from rank-0 tensors, reject rank-0 lhs append, and allow rank-0 rhs append as a scalar element.DirectSumnow throws for rank-0 tensors. The direct sum of scalars is not well defined here; previous behavior treated scalars as rank-1 size-1 tensors and returned a rank-1 size-2 tensor.Kronthelhs_pad_leftbranch prepends ones to rhs’s shape and assigns that to the lhs padded shape. That can make the lhs padded product larger than lhs storage.permute()resized the block vector and then left every blockVoid,so it manufactured bad blocks.permute_nosignflip()in Fermionic has the same diagonal branch bug.[0]and similar. This ought to produce an exception on accessing an axis beyond the rank of the tensor, but in many cases (e.g.Tensordot) would silently read invalid memory if you supply it invalid axes to contract over._from_numpythat does not handle rank-0 numpy arrays.UInt16andInt16to_from_numpyandnumpyfunctions.S.dtype()(real) instead ofin.dtype()(possibly complex) (a real dtype bug for complex-input truncated SVD) and skips allocation when return_err == 0.linalg::Norm(UniTensor)function, that would silently returnDoubleon inputFloattensors (differently to the member functionUniTensor::Normand differently toTensor). This was via initializing the variable to accumulate the block norms vlazeros(1), which defaults toDouble. Fixed by removing the free function and forwarding to the member function.item()on size-one tensors, and the distinction between Tensor singleton broadcasting and symmetry-carrying UniTensors.Notes
I removed the
intoverloads of the C++ generatorszeros()andones(), and changed the array versions to takestd::initializer_list. The problem with theintoverload is thatzeros({}), to create a rank-0 tensor of zeros, would instead call theintoverload and end up aszeros(0). Now, to make a rank-1 tensor you need to writezeros({N}). This is not a problem in Python, which continues to havezeros(N)andzeros([])versions, since Python can distinguish[]and0. This affects a lot of tests that construct rank-1 tensors. There are quite a few of these that look suspicious, egzeros(3 * 4 * 5).reshape({3,4,5}).astype(Types.Double).to(Device.cuda)in a GPU test, and was presumably intended to be the equivalentzeros({3,4,5}, Types.Double, Device.cuda), which has always worked. I fixed all of the ones I saw, and added some more tests of the generators.There is no version bump to the
UniTensorversion number introduced in #1022 - I am assuming that there is no tensor data saved usingmasterbranch in the few hours between that PR landing and this one.AI review notes that there are a few round-trip GPU<->CPU data transfers in the scalar broadcast CUDA kernels. They are all pre-existing, improved in some cases by explicit transfer rather than relying on unified memory, but largely outside the scope of this PR. Those will be addressed by #1003.
Tests
Ran full CPU + GPU tests + pytests