Skip to content

Coalesce and strip explicit zeros before CAN index access - #376

Merged
ffl096 merged 1 commit into
mainfrom
frantzen/harden-can-sparse-inputs
Aug 24, 2026
Merged

Coalesce and strip explicit zeros before CAN index access#376
ffl096 merged 1 commit into
mainfrom
frantzen/harden-can-sparse-inputs

Conversation

@ffl096

@ffl096 ffl096 commented Aug 24, 2026

Copy link
Copy Markdown
Member

LiftLayer and the CAN attention modules call indices() on user-supplied neighborhood matrices. Sparse matrices built with toponetx's from_sparse preserve explicit zero entries (e.g. zero-valued diagonal self-loops emitted by CellComplex.adjacency_matrix), so the number of stored entries does not match the number of true neighborhood relations:

  • uncoalesced input raised RuntimeError on indices()/values()
  • explicit zeros created phantom neighbors in attention softmax
  • LiftLayer produced one signal row per stored entry instead of per edge, mismatching x_1 when concatenating

Add a sanitize_neighborhood helper that coalesces and removes explicit zero entries, and apply it before all index access.

Closes #236 and closes #242

LiftLayer and the CAN attention modules call indices() on user-supplied
neighborhood matrices. Sparse matrices built with toponetx's from_sparse
preserve explicit zero entries (e.g. zero-valued diagonal self-loops
emitted by CellComplex.adjacency_matrix), so the number of stored
entries does not match the number of true neighborhood relations:

- uncoalesced input raised RuntimeError on indices()/values()
- explicit zeros created phantom neighbors in attention softmax
- LiftLayer produced one signal row per stored entry instead of per
  edge, mismatching x_1 when concatenating

Add a sanitize_neighborhood helper that coalesces and removes explicit
zero entries, and apply it before all index access.

References #236, references #242
@ffl096 ffl096 added this to the 0.1.0 milestone Aug 24, 2026
@ffl096 ffl096 self-assigned this Aug 24, 2026
@ffl096 ffl096 added the bug Something isn't working label Aug 24, 2026
@ffl096
ffl096 requested a lite review from Copilot August 24, 2026 12:36
@codecov

codecov Bot commented Aug 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.81818% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 96.36%. Comparing base (aed06d6) to head (d0dc25d).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
topomodelx/nn/cell/can_layer.py 81.81% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #376      +/-   ##
==========================================
- Coverage   96.43%   96.36%   -0.08%     
==========================================
  Files          58       58              
  Lines        2078     2090      +12     
==========================================
+ Hits         2004     2014      +10     
- Misses         74       76       +2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ffl096
ffl096 merged commit d6f6445 into main Aug 24, 2026
37 of 39 checks passed
@ffl096
ffl096 deleted the frantzen/harden-can-sparse-inputs branch August 24, 2026 12:40

Copilot AI 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.

Pull request overview

This PR addresses correctness issues in CAN modules when consuming user-supplied sparse neighborhood matrices that may be uncoalesced and/or contain explicit stored zeros (e.g., produced by toponetx.utils.sparse.from_sparse), which previously led to runtime errors on indices()/values() and phantom neighborhood relations during attention computation.

Changes:

  • Adds a sanitize_neighborhood helper to coalesce sparse COO neighborhoods and drop explicit zero entries.
  • Applies neighborhood sanitization prior to index-based access in LiftLayer and the CAN attention modules to align stored entries with true neighborhood relations.
Suppressed comments (1)

topomodelx/nn/cell/can_layer.py:771

  • Same as above: once sanitize_neighborhood() has stripped explicit zeros, checking emptiness via _nnz() avoids two full scans over values().nonzero() and keeps the early-return logic simpler.
        neighborhood = sanitize_neighborhood(neighborhood)

        # If there are no non-zero values in the neighborhood, then the neighborhood is empty. -> return zero tensor
        if not neighborhood.values().nonzero().size(0) > 0 and self.concat:
            return torch.zeros(

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +37 to +45
neighborhood = neighborhood.coalesce()
values = neighborhood.values()
keep = values != 0
if bool(keep.all()):
return neighborhood
indices = neighborhood.indices()[:, keep]
return torch.sparse_coo_tensor(
indices, values[keep], neighborhood.shape, device=neighborhood.device
).coalesce()
Comment on lines +16 to +24
def sanitize_neighborhood(neighborhood: torch.Tensor) -> torch.Tensor:
r"""Return a coalesced neighborhood matrix without explicit zero entries.

The number of stored entries of the returned tensor equals its number of
true non-zero entries, so that iterating over stored entries (e.g. via
``indices()``) corresponds to actual neighborhood relations. This matters
because sparse matrices built with ``toponetx.utils.sparse.from_sparse``
preserve explicit zeros that other construction paths (e.g. dense casting)
drop.
Comment on lines +567 to 571
neighborhood = sanitize_neighborhood(neighborhood)

# If there are no non-zero values in the neighborhood, then the neighborhood is empty. -> return zero tensor
if not neighborhood.values().nonzero().size(0) > 0 and self.concat:
return torch.zeros(
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix bug in CAN tutorial can_train tutorial fails with new sparse casting

2 participants