Skip to content

Docs#2

Merged
r-fedorov merged 4 commits into
mainfrom
docs
Jul 23, 2026
Merged

Docs#2
r-fedorov merged 4 commits into
mainfrom
docs

Conversation

@r-fedorov

@r-fedorov r-fedorov commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Summary by Sourcery

Document and test the JVP-safe scatter path while adding a full Sphinx documentation site and associated CI workflow.

New Features:

  • Add a jvp_safe mode to scatter_sum for forward-mode differentiation with fixed indices.
  • Introduce an executable documentation contract test suite that validates examples, links, and documented claims.
  • Add a Sphinx-based documentation site with guides, examples, and API reference for the library.

Bug Fixes:

  • Ensure scatter_sum validates index bounds in the JVP-safe path and rejects incompatible use with custom kernels.

Enhancements:

  • Clarify README and evaluation docs around fixed-topology scatter and JVP behavior.
  • Expose correct online documentation URL and configure docs-specific optional dependencies and pytest testpaths in project metadata.
  • Document tensor products, irreps, equivariance testing, performance considerations, and migration from e3nn/PyTorch.

CI:

  • Add a GitHub Actions workflow to build and deploy the Sphinx documentation to GitHub Pages.

Tests:

  • Extend scatter_sum tests to cover the new jvp_safe behavior, gradients, compilation, and validation errors.
  • Add comprehensive tests that enforce documentation–code contracts, validate API surface, and check documentation workflows and timing harnesses.

Summary by CodeRabbit

  • New Features

    • Added comprehensive user, API, development, and example documentation.
    • Added automated documentation builds and publishing to GitHub Pages.
    • Added a JVP-safe scatter reduction mode for forward-mode differentiation.
  • Documentation

    • Clarified MLX compatibility, performance, migration, installation, and deployment guidance.
    • Updated examples to demonstrate JVP-safe scatter usage.
  • Bug Fixes

    • Added validation for safe scatter indices and incompatible execution options.
  • Tests

    • Added extensive checks for documentation links, examples, API references, and documented behavior.

@sourcery-ai

sourcery-ai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds JVP-safe scatter_sum fallback and extensive documentation and tooling for the public docs site, including Sphinx configuration, GitHub Pages deployment, and executable documentation contracts in tests.

Flow diagram for scatter_sum JVP-safe fallback selection

flowchart TD
    start["call scatter_sum(source, index, dim_size, use_custom_kernel, jvp_safe)"]
    validate_dim["validate dim_size and infer when None"]
    check_jvp["jvp_safe?"]
    check_kernel["use_custom_kernel?"]
    error_kernel["raise ValueError(jvp_safe_requires_general_path)"]
    call_fixed["_fixed_index_scatter_sum(source, index, dim_size)"]
    check_metal["mlx_metal_available and use_custom_kernel"]
    custom_kernel["use _scatter_sum_custom_kernel(source, index, dim_size)"]
    general_path["use output.at[index].add(source)"]

    start --> validate_dim --> check_jvp
    check_jvp -->|true| check_kernel
    check_jvp -->|false| check_metal
    check_kernel -->|true| error_kernel
    check_kernel -->|false| call_fixed
    check_metal -->|true| custom_kernel
    check_metal -->|false| general_path
Loading

File-Level Changes

Change Details Files
Extend scatter_sum with a JVP-safe fallback implementation for fixed indices and integrate it into existing validation and test coverage.
  • Add jvp_safe flag to scatter_sum with validation against use_custom_kernel and routing to a new fixed-index implementation.
  • Implement _fixed_index_scatter_sum using host-side stable sort and prefix sums to avoid MLX indexed-add and ensure JVP support.
  • Update existing scatter_sum tests to cover JVP-safe behavior, gradients, JVP correctness, compilation, and input validation for the new mode.
e3nn_mlx/graph.py
tests/test_graph_radial.py
Introduce executable documentation contracts that verify documentation claims, internal links, and documented API/test coverage.
  • Add test_documentation_contract.py to parse markdown, verify code blocks compile, ensure links and toctrees resolve, and assert that documented claims map to live tests and symbols.
  • Embed explicit mappings from human-readable documentation claims to concrete pytest node IDs and source excerpts, enforcing that claims remain in sync with tests and metadata.
  • Check project metadata (pyproject, workflows, eval harness) from tests to ensure packaging, docs, and timing contracts are honored.
tests/test_documentation_contract.py
Set up Sphinx documentation for the project with a user guide, examples, API reference stubs, and development docs, and document JVP-safe scatter and performance behavior.
  • Add Sphinx configuration, Makefile, theme and extension choices, and placeholders for static files and templates.
  • Create high-level docs: index, installation, irreps, tensor products, equivariance, migration, performance, and concrete examples for getting started, convolution, and point models, including discussion of use_custom_kernel and jvp_safe usage.
  • Add development docs covering compatibility contracts, high-level API, deployment, and release processes, and wire them into the docs toctree structure and API index stubs.
docs/conf.py
docs/Makefile
docs/index.md
docs/guide/index.md
docs/guide/installation.md
docs/guide/irreps.md
docs/guide/tensor_products.md
docs/guide/equivariance.md
docs/guide/migration.md
docs/guide/performance.md
docs/examples/index.md
docs/examples/getting_started.md
docs/examples/convolution.md
docs/examples/point_models.md
docs/development/index.md
docs/_static/.gitkeep
docs/_templates/.gitkeep
docs/api/index.md
docs/api/o3.rst
docs/api/nn.rst
docs/api/math.rst
docs/api/graph.rst
docs/api/models.rst
docs/api/typed.rst
Update README and existing docs to describe the new JVP-safe scatter behavior and provide stable links to external guides.
  • Expand README description of scatter_sum to explain jvp_safe semantics, requirements for fixed indices, and the sorted prefix-sum fallback.
  • Clarify HIGH_LEVEL_API references to the evals harness using an explicit GitHub URL and explain performance measurement context.
  • Update kernel evaluation notes to describe fixed-topology scatter JVP behavior and how it interacts with use_custom_kernel and jvp_safe.
README.md
docs/HIGH_LEVEL_API.md
evals/KERNEL_EVALUATION.md
Configure documentation hosting and project metadata, including docs dependencies, pytest configuration, and GitHub Pages workflow.
  • Add docs extra to pyproject with Sphinx and theme dependencies and update the Documentation URL to the GitHub Pages site.
  • Tighten pytest configuration to run tests only from tests/ via testpaths.
  • Create a GitHub Actions workflow to build docs on PRs and deploy to GitHub Pages on main, using the new docs extra and Sphinx build command.
pyproject.toml
.github/workflows/docs.yml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds a Sphinx documentation site with local build tooling and GitHub Pages deployment, expands guides and API references, introduces JVP-safe fixed-index scatter reduction, and adds executable documentation and runtime contract tests.

Changes

Documentation and JVP-safe scatter support

Layer / File(s) Summary
JVP-safe scatter reduction
e3nn_mlx/graph.py, tests/test_graph_radial.py, README.md, evals/KERNEL_EVALUATION.md
scatter_sum adds jvp_safe support through eager fixed indices and a sorted prefix-sum implementation, with validation, gradient, JVP, and compilation coverage.
Documentation build and deployment
.github/workflows/docs.yml, docs/Makefile, docs/conf.py, pyproject.toml, .gitignore, docs/_static/*, docs/_templates/*
Sphinx configuration, documentation dependencies, Make targets, ignored build outputs, and conditional GitHub Pages deployment are added.
Documentation navigation and content
docs/index.md, docs/guide/*, docs/examples/*, docs/api/*, docs/development/*, docs/HIGH_LEVEL_API.md
Project guides, examples, API autosummaries, development notes, deployment instructions, and updated cross-framework links are added.
Executable documentation contracts
tests/test_documentation_contract.py
Tests validate Markdown code blocks, links, toctrees, API targets, public namespaces, configuration claims, documented examples, equivariance, numerical behavior, and dtype contracts.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant scatter_sum
  participant _fixed_index_scatter_sum
  participant MLX
  Caller->>scatter_sum: pass jvp_safe=True
  scatter_sum->>_fixed_index_scatter_sum: dispatch fixed-index reduction
  _fixed_index_scatter_sum->>MLX: compute sorted prefix sums
  MLX-->>Caller: return reduced values and JVP-compatible results
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is too generic to describe the documentation workflow, API pages, and test additions in this change. Rename it to a specific summary like “Add Sphinx docs, examples, and documentation contract tests”.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch docs

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Checkov (3.3.8)
.github/workflows/docs.yml

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai sourcery-ai Bot 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.

Hey - I've found 1 issue, and left some high level feedback:

  • The _fixed_index_scatter_sum fallback performs np.searchsorted over np.arange(dim_size), which is O(dim_size) even for very sparse indices; consider constraining this to the actual min/max occupied rows or documenting the runtime/memory tradeoff for large dim_size.
  • _fixed_index_scatter_sum imports NumPy inside the function on every call; moving this import to module scope would avoid repeated import overhead and make the function cheaper in hot paths that rely on jvp_safe=True.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `_fixed_index_scatter_sum` fallback performs `np.searchsorted` over `np.arange(dim_size)`, which is O(dim_size) even for very sparse indices; consider constraining this to the actual min/max occupied rows or documenting the runtime/memory tradeoff for large `dim_size`.
- `_fixed_index_scatter_sum` imports NumPy inside the function on every call; moving this import to module scope would avoid repeated import overhead and make the function cheaper in hot paths that rely on `jvp_safe=True`.

## Individual Comments

### Comment 1
<location path="docs/guide/installation.md" line_range="32-33" />
<code_context>
+python -m pytest
+```
+
+MLX tests that require a working runtime can otherwise skip. CI prevents an
+accidental pass by setting `E3NN_MLX_REQUIRE_RUNTIME=1`.
+
</code_context>
<issue_to_address>
**suggestion (typo):** Consider rephrasing this sentence for clearer grammar.

The phrase "can otherwise skip" is a bit unclear, as it sounds like the tests perform the action. Consider changing it to "can otherwise be skipped" or "can otherwise be omitted" for more natural phrasing.

```suggestion
MLX tests that require a working runtime can otherwise be skipped. CI prevents an
accidental pass by setting `E3NN_MLX_REQUIRE_RUNTIME=1`.
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +32 to +33
MLX tests that require a working runtime can otherwise skip. CI prevents an
accidental pass by setting `E3NN_MLX_REQUIRE_RUNTIME=1`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (typo): Consider rephrasing this sentence for clearer grammar.

The phrase "can otherwise skip" is a bit unclear, as it sounds like the tests perform the action. Consider changing it to "can otherwise be skipped" or "can otherwise be omitted" for more natural phrasing.

Suggested change
MLX tests that require a working runtime can otherwise skip. CI prevents an
accidental pass by setting `E3NN_MLX_REQUIRE_RUNTIME=1`.
MLX tests that require a working runtime can otherwise be skipped. CI prevents an
accidental pass by setting `E3NN_MLX_REQUIRE_RUNTIME=1`.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/conf.py`:
- Line 10: Add a targeted Ruff suppression for the required global copyright
setting in docs/conf.py, preferably an inline # noqa: A001 on the copyright
assignment; do not alter the Sphinx variable or broaden lint ignores.

In `@docs/development/deployment.md`:
- Around line 20-23: Revise the deployment documentation to avoid claiming the
pull-request documentation build is required unless repository branch protection
or ruleset configuration is explicitly documented. Prefer stating that PRs build
the site, or add the corresponding required-check configuration details.

In `@docs/examples/point_models.md`:
- Around line 40-48: Update the forward helper around model.forward_with_edges
to derive num_graphs from the supplied graph_batch instead of hard-coding 1,
ensuring batched inputs produce the correct pooling output size; alternatively,
explicitly enforce and document a single-graph graph_batch precondition.

In `@docs/guide/installation.md`:
- Around line 37-41: Update the scatter guidance in the installation
documentation to state that forward-mode JVPs require `jvp_safe=True` with
eager, fixed indices; do not imply that `use_custom_kernel=False` alone provides
JVP support. Preserve the existing general-operation guidance and distinguish it
from the JVP-safe execution path.

In `@e3nn_mlx/graph.py`:
- Around line 83-87: Update the row-label construction in the graph grouping
logic around order_host and the starts_host/ends_host searchsorted calls to use
a wide signed dtype instead of host_index.dtype, preventing valid narrow
unsigned indices from wrapping when dim_size exceeds their range. Add a
regression test covering narrow unsigned indices with dim_size=257 and verify
row 256 remains in its own bucket.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 93dda358-a21e-4716-a10a-61a91f1d728d

📥 Commits

Reviewing files that changed from the base of the PR and between 79735e8 and e6bb8b7.

📒 Files selected for processing (34)
  • .github/workflows/docs.yml
  • .gitignore
  • README.md
  • docs/HIGH_LEVEL_API.md
  • docs/Makefile
  • docs/_static/.gitkeep
  • docs/_templates/.gitkeep
  • docs/api/graph.rst
  • docs/api/index.md
  • docs/api/math.rst
  • docs/api/models.rst
  • docs/api/nn.rst
  • docs/api/o3.rst
  • docs/api/typed.rst
  • docs/conf.py
  • docs/development/deployment.md
  • docs/development/index.md
  • docs/examples/convolution.md
  • docs/examples/getting_started.md
  • docs/examples/index.md
  • docs/examples/point_models.md
  • docs/guide/equivariance.md
  • docs/guide/index.md
  • docs/guide/installation.md
  • docs/guide/irreps.md
  • docs/guide/migration.md
  • docs/guide/performance.md
  • docs/guide/tensor_products.md
  • docs/index.md
  • e3nn_mlx/graph.py
  • evals/KERNEL_EVALUATION.md
  • pyproject.toml
  • tests/test_documentation_contract.py
  • tests/test_graph_radial.py

Comment thread docs/conf.py

project = "e3nn-mlx"
author = "e3nn-mlx contributors"
copyright = "2026, lamalab-org"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Files matching conf.py/build/lint config:\n'
git ls-files | rg '(^|/)(docs/conf\.py|pyproject\.toml|ruff\.toml|\.ruff\.toml|tox\.ini|pre-commit\.config\.yaml|noxfile\.py)$' || true

printf '\ndocs/conf.py excerpt:\n'
if [ -f docs/conf.py ]; then
  sed -n '1,80p' docs/conf.py | cat -n
fi

printf '\nruff-related occurrences:\n'
rg -n "ruff|per-file-ignores|rule-select|ignore|noqa" pyproject.toml ruff.toml .ruff.toml pre-commit.config.yaml tox.ini docs/conf.py 2>/dev/null || true

printf '\nSearch for A001/copyright variable in tracked files:\n'
rg -n "A001|builtins\.copyright|noqa|copyright" -S .

Repository: lamalab-org/e3nn_mlx

Length of output: 1969


Supress Ruff A001 for Sphinx’s required setting.

Sphinx’s docs/conf.py requires the global copyright variable, but Ruff will see it as shadowing the built-in. Add a targeted # noqa: A001 or per-file ignore for docs/conf.py.

Proposed fix
-copyright = "2026, lamalab-org"
+copyright = "2026, lamalab-org"  # noqa: A001
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
copyright = "2026, lamalab-org"
copyright = "2026, lamalab-org" # noqa: A001
🧰 Tools
🪛 Ruff (0.15.21)

[error] 10-10: Variable copyright is shadowing a Python builtin

(A001)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/conf.py` at line 10, Add a targeted Ruff suppression for the required
global copyright setting in docs/conf.py, preferably an inline # noqa: A001 on
the copyright assignment; do not alter the Sphinx variable or broaden lint
ignores.

Source: Linters/SAST tools

Comment on lines +20 to +23
The repository includes `.github/workflows/docs.yml`. Pull requests build the
site as a required-quality check but do not deploy it. A push to `main` that
changes documentation, package source, or documentation configuration builds
and publishes the Pages artifact.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Do not imply that the PR check is required without repository enforcement.

The workflow can build documentation for pull requests, but GitHub only makes that check mandatory when branch protection or a ruleset marks it as required. Either reword this as “PRs build the site” or document the required-check configuration.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~20-~20: The official name of this software platform is spelled with a capital “H”.
Context: ...h GitHub Pages The repository includes .github/workflows/docs.yml. Pull requests buil...

(GITHUB)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/development/deployment.md` around lines 20 - 23, Revise the deployment
documentation to avoid claiming the pull-request documentation build is required
unless repository branch protection or ruleset configuration is explicitly
documented. Prefer stating that PRs build the site, or add the corresponding
required-check configuration details.

Comment on lines +40 to +48
def forward(pos, features, graph_batch, src, dst):
return model.forward_with_edges(
pos,
features,
graph_batch,
src,
dst,
num_graphs=1,
).array

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Derive num_graphs from the supplied batch.

SimpleNetwork.forward_with_edges uses num_graphs as the output size for node pooling. Hard-coding 1 breaks valid batched inputs whose graph_batch contains more than one graph. Compute the count outside the compiled function for the fixed batch, or explicitly document and assert a single-graph precondition. (raw.githubusercontent.com)

Proposed fix
+num_graphs = (
+    0 if data["batch"].shape[0] == 0
+    else int(mx.max(data["batch"]).item()) + 1
+)
+
 def forward(pos, features, graph_batch, src, dst):
     return model.forward_with_edges(
         pos,
         features,
         graph_batch,
         src,
         dst,
-        num_graphs=1,
+        num_graphs=num_graphs,
     ).array
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/examples/point_models.md` around lines 40 - 48, Update the forward
helper around model.forward_with_edges to derive num_graphs from the supplied
graph_batch instead of hard-coding 1, ensuring batched inputs produce the
correct pooling output size; alternatively, explicitly enforce and document a
single-graph graph_batch precondition.

Comment on lines +37 to +41
Generated kernels are enabled by default on compatible Apple-silicon inputs.
Every public operation retains a general MLX path. For spherical harmonics and
scatter, pass `use_custom_kernel=False`; for tensor products, construct the
module with `use_custom_kernel=False` or call `differentiable_arrays` when a
forward-mode transform is needed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Document jvp_safe=True for scatter JVPs.

scatter_sum(..., use_custom_kernel=False) still uses the indexed-add path, which has no JVP rule. Forward-mode differentiation requires jvp_safe=True with eager, fixed indices; update this guidance to distinguish general execution from the JVP-safe path. (raw.githubusercontent.com)

Proposed wording
-For spherical harmonics and scatter, pass `use_custom_kernel=False`; for tensor
-products, construct the module with `use_custom_kernel=False` or call
-`differentiable_arrays` when a forward-mode transform is needed.
+For spherical harmonics, pass `use_custom_kernel=False`. For `scatter_sum`,
+use `jvp_safe=True` with eager, fixed indices when forward-mode differentiation
+is needed; this path requires `use_custom_kernel=False`. For tensor products,
+construct the module with `use_custom_kernel=False` or call
+`differentiable_arrays` when a forward-mode transform is needed.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Generated kernels are enabled by default on compatible Apple-silicon inputs.
Every public operation retains a general MLX path. For spherical harmonics and
scatter, pass `use_custom_kernel=False`; for tensor products, construct the
module with `use_custom_kernel=False` or call `differentiable_arrays` when a
forward-mode transform is needed.
Generated kernels are enabled by default on compatible Apple-silicon inputs.
Every public operation retains a general MLX path. For spherical harmonics, pass `use_custom_kernel=False`. For `scatter_sum`,
use `jvp_safe=True` with eager, fixed indices when forward-mode differentiation
is needed; this path requires `use_custom_kernel=False`. For tensor products,
construct the module with `use_custom_kernel=False` or call `differentiable_arrays`
when a forward-mode transform is needed.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/guide/installation.md` around lines 37 - 41, Update the scatter guidance
in the installation documentation to state that forward-mode JVPs require
`jvp_safe=True` with eager, fixed indices; do not imply that
`use_custom_kernel=False` alone provides JVP support. Preserve the existing
general-operation guidance and distinguish it from the JVP-safe execution path.

Comment thread e3nn_mlx/graph.py
Comment on lines +83 to +87
order_host = np.argsort(host_index, kind="stable")
sorted_index = host_index[order_host]
rows = np.arange(dim_size, dtype=host_index.dtype)
starts_host = np.searchsorted(sorted_index, rows, side="left")
ends_host = np.searchsorted(sorted_index, rows, side="right")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
python - <<'PY'
import numpy as np

index = np.array([0, 1], dtype=np.uint8)
rows = np.arange(257, dtype=index.dtype)
assert rows[256] == 0  # Demonstrates the erroneous wrapped lookup label.
PY

Repository: lamalab-org/e3nn_mlx

Length of output: 158


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
git ls-files | rg '(^|/)e3nn_mlx/graph\.py$' || true

echo "== file context =="
if [ -f e3nn_mlx/graph.py ]; then
  wc -l e3nn_mlx/graph.py
  sed -n '1,140p' e3nn_mlx/graph.py | cat -n
fi

echo "== relevant occurrences =="
rg -n "def .*|host_index|order_host|sorted_index|rows = |searchsorted|dim_size" e3nn_mlx/graph.py || true

echo "== behavioral probe of NumPy semantics with uint8 rows =="
python3 - <<'PY'
import numpy as np

for dtype in [np.uint8, np.uint16, np.int8, np.int16, np.int64]:
    for rows_max in [255, 256, 257, 766, 768]:
        rows = np.arange(rows_max, dtype=dtype)
        wrapped = rows[-1] if int(rows_max) < 2**64 or dtype in (np.int8, np.int16) else None
        print(dtype, rows_max, wrapped)
PY

Repository: lamalab-org/e3nn_mlx

Length of output: 7772


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== repo-wide occurrences of jvp_safe scatter =="
rg -n "jvp_safe|\bscatter_sum\(" . --glob '!*.log' || true

echo "== tests mentioning fixed_index or scatter_sum =="
rgitz -n "fixed_index_scatter_sum|_fixed_index_scatter_sum|scatter_sum|jvp_safe|uint8.*scatter|dim_size.*256|dim_size.*257" --glob '!*.log' . || true

echo "== file size and test files =="
wc -l e3nn_mlx/graph.py
git ls-files | rg '(^|/)(test_|tests|.*test.*\.py$)' || true

Repository: lamalab-org/e3nn_mlx

Length of output: 5897


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== tests/test_graph_radial.py around scatter_sum =="
sed -n '100,185p' tests/test_graph_radial.py | cat -n

echo "== tests/test_documentation_contract.py around scatter_sum =="
sed -n '600,660p' tests/test_documentation_contract.py | cat -n

echo "== tests/test_metal_kernels.py around scatter_sum =="
sed -n '95,125p' tests/test_metal_kernels.py | cat -n

Repository: lamalab-org/e3nn_mlx

Length of output: 8511


Avoid wrapping row labels for narrow index dtypes.

rows inherits host_index.dtype, so valid uint8 indices can wrap at dim_size=257 and send the 256th row into bucket 0. Use a wide signed dtype for the lookup labels and add a regression test for narrow unsigned indices.

Proposed fix
-    rows = np.arange(dim_size, dtype=host_index.dtype)
+    rows = np.arange(dim_size, dtype=np.int64)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
order_host = np.argsort(host_index, kind="stable")
sorted_index = host_index[order_host]
rows = np.arange(dim_size, dtype=host_index.dtype)
starts_host = np.searchsorted(sorted_index, rows, side="left")
ends_host = np.searchsorted(sorted_index, rows, side="right")
order_host = np.argsort(host_index, kind="stable")
sorted_index = host_index[order_host]
rows = np.arange(dim_size, dtype=np.int64)
starts_host = np.searchsorted(sorted_index, rows, side="left")
ends_host = np.searchsorted(sorted_index, rows, side="right")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e3nn_mlx/graph.py` around lines 83 - 87, Update the row-label construction in
the graph grouping logic around order_host and the starts_host/ends_host
searchsorted calls to use a wide signed dtype instead of host_index.dtype,
preventing valid narrow unsigned indices from wrapping when dim_size exceeds
their range. Add a regression test covering narrow unsigned indices with
dim_size=257 and verify row 256 remains in its own bucket.

@r-fedorov
r-fedorov merged commit 726b6e1 into main Jul 23, 2026
10 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Jul 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant