Conversation
…(possibly with bonds removed)
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #714 +/- ##
==========================================
+ Coverage 35.44% 35.65% +0.20%
==========================================
Files 215 214 -1
Lines 33071 33068 -3
Branches 13170 13157 -13
==========================================
+ Hits 11723 11789 +66
+ Misses 19424 19349 -75
- Partials 1924 1930 +6 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
Is it possible to add some simple unit test? |
e65f904 to
25121c9
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 25121c9397
ℹ️ 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".
set behaves like get for diagonal UniTenosrs get always returns a UniTensor with independent metadata no memory leak with empty 'removed' argument using get()
…th two accessor elements on a diagonal UniTensor this creates consistency; use one accessor element to address only the diagonal elements;
|
All issues were fixed, and unit tests were added. I changed the behavior so that slicing a diagonal-dense UniTensor with two accessors always yields a non-diagonal UniTensor. Before, the result was still diagonal if the two accessors were the same, but this led to inconsistent behavior. If the output should be diagonal, use only one accessor! |
pcchen
left a comment
There was a problem hiding this comment.
PR Review: changed get() and slicing
Posted by Claude Code on behalf of @pcchen
Summary
This PR extends get() and slicing for UniTensor:
- Diagonal UniTensors can now be sliced with two-dimensional accessors (converting to dense)
UniTensor::operator[]overloads are addedAccessor::operator==is added- Bond metadata (labels, rowrank) is now preserved through slicing
The approach is sound and well-tested. There are a few bugs and style issues to address before merging.
Critical Issues
1. Memory leak in DenseUniTensor::get() — diagonal two-accessor path
src/DenseUniTensor.cpp, in the else branch for 2 accessors:
DenseUniTensor *out = this->clone_meta(); // allocates raw pointer
...
} else { // convert to non-diagonal UniTensor
out->_block = this->_block;
out->to_dense_();
return out->get(accessors); // 'out' is NEVER freed — memory leak
}out->get(accessors) returns a new boost::intrusive_ptr<UniTensor_base> wrapping a different object. The out raw pointer is never placed in an intrusive_ptr, so it leaks on this path.
Fix: wrap out before the recursive call:
boost::intrusive_ptr<DenseUniTensor> out_ptr(out);
out_ptr->_block = this->_block;
out_ptr->to_dense_();
return out_ptr->get(accessors);2. Wrong error tag in DenseUniTensor::set()
src/DenseUniTensor.cpp, set() method, two-accessor diagonal path:
"[ERROR][DenseUniTensor][get] For diagonal UniTensors, only one or two "The tag says [get] but this is the set() function. Should be [set].
Important Issues
3. Type inconsistency: cytnx_int32 vs cytnx_int64 for removed
src/backend/Tensor_impl.cpp, set() path:
- std::vector<cytnx_int32> remove_id;
+ std::vector<cytnx_int32> removed; // still int32!The rename was done but the type stayed cytnx_int32, inconsistent with get() which uses cytnx_int64. While this is a local variable with no external impact, it should be consistent.
4. _is_braket_form update is one-sided
src/DenseUniTensor.cpp, non-diagonal path:
if (out->is_tag() && !out->_is_braket_form) {
out->_is_braket_form = out->_update_braket();
}This only promotes _is_braket_form from false to true, never the reverse. Slicing can also invalidate a previously valid braket form (e.g., if bonds are removed unevenly). Should be:
if (out->is_tag()) {
out->_is_braket_form = out->_update_braket();
}Minor Issues
5. Commented-out dead code in pybind/unitensor_py.cpp
The old error path is left as commented code in three places:
// cytnx_error_msg(true,
// "[ERROR] cannot get element using [tuple] on is_diag=True UniTensor since the block is rank-1, consider [int] or [int:int] instead.%s", "\n");Please remove these.
6. Unused variable cnt in pybind/unitensor_py.cpp
In all three copies of the new diagonal slicing code block:
cytnx_uint64 cnt = 0;
for (...) {
cnt++; // incremented but never readcnt should be removed.
7. Triplicated code in pybind/unitensor_py.cpp
The diagonal two-accessor handling block is copy-pasted verbatim three times (for __getitem__, a second __getitem__ variant, and __setitem__). Consider extracting a helper lambda or function to reduce maintenance burden.
Positive Observations
- The
removedout-parameter approach for propagating squeezed dimensions fromTensor_implup toDenseUniTensoris clean and doesn't break the public API. - Test coverage is thorough: single/double accessor cases, diagonal/non-diagonal, get/set/operator[], and immutability of source.
- The
AreEqUniTensorMetahelper is a useful addition. - Fixing
get/set_block→get/put_blockin error messages is a good cleanup. - The
Accessor::operator==implementation is correct and well-tested.
Recommended Actions
- Fix the memory leak (item 1) — critical, causes heap corruption in the diagonal two-accessor
get()path. - Fix the wrong
[get]tag inset()(item 2). - Fix
_is_braket_formupdate (item 4). - Remove commented-out code and unused
cnt(items 5–6). - Consider extracting the duplicated pybind diagonal slice code (item 7).
DenseUniTensor.cpp: Pointer in get() freed before return DenseUniTensor.cpp: Type consistency for varibable `removed` unitensor_py.cpp: Using Lambda to avoid code repetition for bindings __setitem__ and __getitem__ Typos fixed, cleanup, removed unused variable cnt
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b527d89b17
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| } | ||
|
|
||
| boost::intrusive_ptr<UniTensor_base> DenseUniTensor::get(const std::vector<Accessor> &accessors) { | ||
| if (accessors.empty()) return this->clone_meta(); |
There was a problem hiding this comment.
Populate block when returning get({}) clone
Even though aliasing was removed, this new get fast path returns clone_meta() without copying _block, so UniTensor::get({}) can produce a tensor with metadata but no backing data. That makes subsequent reads inconsistent (and diagonal shape/data access can fail because _is_diag is preserved while _block is empty), and Python diagonal ut[()] can hit this path via the new accessor builder.
Useful? React with 👍 / 👎.
| } | ||
|
|
||
| void DenseUniTensor::set(const std::vector<Accessor> &accessors, const Tensor &rhs) { | ||
| if (accessors.empty()) return; |
There was a problem hiding this comment.
Apply empty-accessor assignments instead of returning early
This early return makes DenseUniTensor::set silently ignore set({}, rhs), which is a behavior regression from forwarding to Tensor::set (where an empty accessor list means full-slice assignment after axis fill). With the new Python accessor logic, diagonal ut[()] = rhs can now become a no-op rather than updating data.
Useful? React with 👍 / 👎.
| for (unsigned int j = 0; j < removed.size(); j++) { | ||
| if (new_mapper[i] > removed[j]) |
There was a problem hiding this comment.
Exclude stale removed indices from get() permutation
Tensor_impl::get(accessors, removed) now uses the caller-provided removed vector directly when building perm, so any pre-existing entries in that output vector influence the returned tensor layout. Since this API is documented to append removed axes, callers can reasonably reuse a non-empty vector across calls, and stale indices will incorrectly drop/shift dimensions in unrelated slices.
Useful? React with 👍 / 👎.
|
UniTensors keep their metadata (possibly with bonds removed for constant access of one element in a given dimension).
Diagonal UniTensors can be accessed by two-dimensional slicing as well now.
This fixes #397