feat(coloring)!: JIT with numba, port SMC graph layer, track star sets#104
Merged
Conversation
- `color_symmetric` now returns `(colors, num_colors, star_set)` instead of `(colors, num_colors)`. The new `StarSet` stores per-edge star assignment and per-star hub vertex, enabling O(1) hub lookup during Hessian decompression. - Add `postprocess=True` (default) which demotes colors never needed as a hub to neutral (`-1`), reducing HVP count. - Add `forced_colors` kwarg to verify a user-supplied coloring against the star-coloring constraints; raises the new `InvalidColoringError` on violation. - Promote private validators to public API: `check_coloring_rows`, `check_coloring_cols`, `check_coloring_symmetric` (in `verify.py`). - `ColoredPattern` gains an optional `star_set` field; decompression uses hub-based extraction when present and falls back to the existing uniqueness heuristic otherwise. - `color_rows` and `color_cols` are unchanged. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Drop `SMC` reference from `color_symmetric` docstring and cite Gebremedhin et al. (2007), Algorithm 4.1 directly. - Add backticks to `True` in the `postprocess` docstring. - Rename section headers from `Public API: ...` to plain descriptive headers so they don't imply `StarSet` above isn't public. - Add a permalink to the SMC stamp-trick line in the source comment. - Drop the three `_is_valid_*_coloring` test helpers; call `check_coloring_rows` / `check_coloring_cols` / `check_coloring_symmetric` directly — the validators already raise `InvalidColoringError` with the specific violation. - Drop seed reporting from the fuzz test since the validator error already identifies the offending vertex/edge. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Inline the trivial-star hub encoding notes (previously a floating comment block) into the `StarSet.hub` attribute docstring. - Drop the `(Gebremedhin et al., 2007)` citation from `_update_stars`: `_update_stars!` is SMC's own function name, not from the paper. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #104 +/- ##
==========================================
- Coverage 98.42% 93.28% -5.14%
==========================================
Files 41 48 +7
Lines 2342 2606 +264
==========================================
+ Hits 2305 2431 +126
- Misses 37 175 +138 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Groups the module's contents into five private submodules behind an `__init__.py` re-export: - `_types.py`: `StarSet`, `DenseColoringWarning`, `InvalidColoringError` - `_color_greedy.py`: `color_rows`, `color_cols` + conflict/greedy helpers - `_color_symmetric.py`: `color_symmetric` + adjacency/star helpers - `_postprocessing.py`: `_postprocess_star_coloring` - `_api.py`: `jacobian_coloring*`, `hessian_coloring*` + empty/dense helpers The public surface of `asdex.coloring` is unchanged — every name in `__all__` still resolves via `__init__.py` re-exports. `tests/test_coloring.py` moves its `_greedy_color` import to `asdex.coloring._color_greedy`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add the SparseMatrixColorings.jl attribution block plus a link to the corresponding SMC source file in each submodule docstring of `src/asdex/coloring/`. Also correct `_types.py`, which previously claimed `StarSet` lived in `forest.jl` — it is actually defined in `coloring.jl`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Convert `detection.py` into a package mirroring `coloring/`: `detection.py` → `detection/_api.py`, `_interpret/` → `detection/_interpret/`. The interpreter is consumed only by `detection`, so nesting it under its sole consumer reflects the dependency direction and keeps the package root focused on pipeline stages. Updates imports in `decompression.py` and `tests/_interpret/test_internals.py`, and path references in `CLAUDE.md`, `tests/CLAUDE.md`, and `.claude/skills/add-handler/SKILL.md`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduces `_graph.py` mirroring SparseMatrixColorings.jl's `graph.jl` (`_build_csr`, `_build_symmetric_csr`, `_build_edge_to_index`) and rewrites both coloring paths on top of it. - `_color_greedy.py` now mirrors `partial_distance2_coloring` faithfully. Drops the O(Σ deg²) `list[set[int]]` conflict graphs and per-vertex `set[int]` allocations for SMC's `forbidden_colors` timestamp trick. LargestFirst uses SMC's `visited`-timestamp trick. `color_rows` and `color_cols` share one `_partial_distance2_coloring` helper. - `_color_symmetric.py`'s `_build_adjacency_with_edge_index` (list-of-tuples + `dict[(i, j), int]`) is replaced by `_build_symmetric_csr` + `_build_edge_to_index`; `_treat` and `_update_stars` iterate CSR slices. Public API and `StarSet` contract unchanged. `_greedy_color` removed; its zero-vertex test is covered by `test_color_zero_row_pattern`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The SMC port in `_color_greedy.py` and `_color_symmetric.py` iterated CSR arrays (`indptr`, `neighbors`, `edge_to_index`) and stamp buffers (`forbidden`, `visited`, `colors`, `star`) as numpy arrays, with per-element `int(...)` boxing in the triple-nested loops. That boxing dominates in CPython and made `color_rows` 4-7x slower than `main`'s conflict-set approach. Rebind the hot arrays to Python lists via `.tolist()` at the top of each function, run the loop in unboxed-int territory, and re-wrap as numpy only at the public boundary. No algorithmic change; all 93 coloring tests pass. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Closes the 1.2-1.6× perf gap vs `main` left by the list-based port in 0a12bf0 and goes further — post-compile timings beat `main` by 6-9×: | bench | main | previous | new | | ---------- | ------- | -------- | -------- | | convnet | 2.22 ms | 3.53 ms | 245 µs | | heat | 158 µs | 197 µs | 23.5 µs | | rosenbrock | 159 µs | 194 µs | 21.5 µs | Changes: - Add `numba>=0.63.0` as a required dependency; ignore `*.nbi` / `*.nbc`. - `@njit(cache=True)` the hot kernels: `_build_edge_to_index_core` in `_graph.py`; `_distance2_degrees` and `_partial_distance2_coloring` in `_color_greedy.py`; `_star_coloring_core` (plus `_treat` and `_update_stars`) in `_color_symmetric.py`. - Drop the `.tolist()` rebinding; numba reads int32 arrays directly. - Refactor for numba compatibility in `_color_symmetric.py`: preallocated `hub_buf` + `hub_len` counter instead of `hub_list`; three parallel `int32[n]` arrays (`fn_p`, `fn_q`, `fn_edge`) instead of a `first_neighbor` tuple list; `InvalidColoringError` piped via return sentinels so the jitted core never raises. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Call `color_rows(sparsity)` once before `benchmark(...)` in the three coloring benchmarks so the first-call compile pass is never folded into the measured rounds. Previously the timings relied on pytest-benchmark's calibration phase incidentally triggering the JIT — now the warmup is explicit, matching the `# warmup` pattern used by the materialization and end-to-end benchmarks. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Align `color_symmetric`'s default with SparseMatrixColorings.jl's `GreedyColoringAlgorithm` (`postprocessing=false`), and thread a `postprocess` kwarg through `jacobian_coloring`, `hessian_coloring`, and their `_from_sparsity` counterparts so callers can opt in. Add tests exercising both paths on a 4-cycle without a diagonal (where postprocessing strictly reduces 3 colors to 2) and verifying the no-op case on a banded pattern with full diagonal. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add tests covering the previously unreached branches of `jacobian_coloring_from_sparsity`, `hessian_coloring_from_sparsity`, `color_symmetric(forced_colors=...)`, `StarSet.hub_vertex`, and both branches of trivial-star postprocessing. Each new test makes concrete assertions (mode resolution, HVP roundtrips, forced-coloring violations, hub-flip identity) instead of merely exercising code paths. Remove guards that are unreachable under their public-API contracts: the non-square check in `_empty_hessian_pattern`, the unused `nb_self_loops` counter in `_build_edge_to_index`, and the `num_colors == 0` / `ci < 0` / `s < 0` branches in `_postprocess_star_coloring`. Coverage (with `NUMBA_DISABLE_JIT=1`) is now 100% across `src/asdex/coloring/`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Breaking
color_symmetricnow returns(colors, num_colors, star_set)instead of(colors, num_colors). The newStarSetstores per-edge star assignment and per-star hub vertex, enabling O(1) hub lookup during Hessian decompression.numba>=0.63.0.API additions
postprocess=True(default) tocolor_symmetric, which demotes colors never needed as a hub to neutral (-1), reducing HVP count.forced_colorskwarg tocolor_symmetricto verify a user-supplied coloring against star-coloring constraints; raises the newInvalidColoringErroron violation.check_coloring_rows,check_coloring_cols,check_coloring_symmetric(inverify.py).ColoredPatterngains an optionalstar_setfield; decompression uses hub-based extraction when present and falls back to the existing uniqueness heuristic otherwise.Internal refactor
coloring.pyinto_graph.py,_color_greedy.py,_color_symmetric.py,_postprocessing.py,_types.py,_api.py.detection/_interpret/._build_csr,_build_symmetric_csr,_build_edge_to_index) as the shared foundation for distance-2 and star coloring.color_symmetricdocstring; inline the trivial-star hub encoding into theStarSet.hubdocstring.Performance
Coloring benchmarks (mean, `N = 200`):
Test plan