Skip to content

Fix Massey product panic on proper multiplication kernels (#116)#258

Merged
JoeyBF merged 5 commits into
masterfrom
claude/fix-116
Jul 4, 2026
Merged

Fix Massey product panic on proper multiplication kernels (#116)#258
JoeyBF merged 5 commits into
masterfrom
claude/fix-116

Conversation

@JoeyBF

@JoeyBF JoeyBF commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Fixes #116.

Problem

ExtAlgebra::massey_iter_c and the single-shot ExtAlgebra::massey panic with Failed to lift (or, in release, the apply_quasi_inverse assertion) whenever the kernel of "multiply by b" at a third-factor bidegree is a proper subspace. The issue's reproduction still triggers it:

cargo run --example massey -- S_2 "" "" "" 14 2 "[1]" 14 4 "[1]"

(i.e. a = h3², b = d0), panicking at ext/src/chain_complex/chain_homotopy.rs:247.

Root cause

The triple Massey product <a, b, c> is defined only when a·b = 0 and b·c = 0. massey_at built the null-homotopy of the composite f_b ∘ f_genᵢ per generator genᵢ of the third-factor bidegree, stored the per-generator bracket values in an answers matrix, and only afterwards computed the kernel of ·b, combining answers linearly over the kernel rows.

But the null-homotopy of f_b ∘ f_genᵢ (multiplication by b·genᵢ) exists only when b·genᵢ = 0 in Ext. When some generator is not individually killed by b (the kernel is a proper subspace), ChainHomotopy::extend cannot lift and panics. The per-generator "answers matrix, then combine over the kernel" design is fundamentally invalid for proper kernels: the bracket is only defined on the kernel and can only be realised by building the homotopy for the actual kernel class. This only surfaced for a first factor of filtration s ≥ 2 (the cycle assertion at the homotopy's top step is gated on target.s() > 1 == a.s() > 1), which is why the existing tests — all using h0 — never hit it.

Fix

Restructure both entry points to realise the actual kernel class via ResolutionHomomorphism::from_class and build a single valid null-homotopy, mirroring the already-correct massey_iter_a:

  • massey_kernel — the valid third factors (kernel of ·b), computed from the product maps alone (no homotopy).
  • massey_bracket_of — realises one kernel class and reads off its bracket.
  • massey now verifies b·c = 0 up front and never attempts to lift an invalid third factor.

massey_at/MasseyComputeDatum are removed; massey_iter_a and the shared massey_result/massey_representative/massey_indeterminacy helpers are unchanged.

This is not more expensive: massey_iter_c now builds one null-homotopy per kernel basis element (dim(ker) ≤ num_gens) instead of one per generator.

Testing

  • The issue's reproduction command now completes cleanly (exit 0) instead of panicking.
  • cargo clippy -p ext --lib is clean; all ext library tests pass.
  • New regression tests (both fit the (6,5) resolution already used by the existing tests):
    • <h1², h0, h0> returns None without panicking (first factor s = 2, exercising the single-shot path).
    • massey_iter_c(h1², h0) completes and agrees with the independent massey_iter_a(h0, h1²).

🤖 Generated with Claude Code


Generated by Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved Massey product calculations by switching to a kernel-first approach for computing the valid third-factor.
    • Added stricter input validation so invalid third-factor conditions now return no result without panicking.
    • Fixed iteration over Massey products so it no longer stops early on problematic cases.
  • Tests
    • Added regression coverage for edge cases (including higher filtration and normalization) to ensure consistent results.

`ExtAlgebra::massey_iter_c` and the single-shot `massey` built the
null-homotopy of `b ∘ c` per *generator* of the third-factor bidegree,
then computed the kernel of `·b` only afterwards. The null-homotopy of
`b ∘ gen_i` exists only when `b · gen_i = 0`, so whenever the kernel was
a proper subspace (some generator not individually killed by `b`) the
lift could not complete and `ChainHomotopy::extend` panicked ("Failed to
lift"). This surfaced for first factors of filtration `s >= 2`, e.g.
`<h3^2, d0, ->`.

Restructure both to realise the *actual* kernel class via
`ResolutionHomomorphism::from_class` and build a single valid
null-homotopy, mirroring the already-correct `massey_iter_a`:

- `massey_kernel` computes the valid third factors (kernel of `·b`) from
  the product maps alone, no homotopy.
- `massey_bracket_of` realises one kernel class and reads its bracket.
- `massey` now verifies `b · c = 0` up front and never lifts an invalid
  third factor.

This is no more expensive: `iter_c` now builds one homotopy per kernel
basis element (`dim(ker) <= num_gens`) instead of one per generator.
`massey_iter_a` is unchanged.

Adds regression tests: `<h1^2, h0, h0>` returns `None` without panicking,
and `massey_iter_c(h1^2, h0)` completes and agrees with `massey_iter_a`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CAgVE4H22yxYTjSrH3r1iJ
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 93ed6a9f-fd60-4e7a-b4cc-4093db4815d5

📥 Commits

Reviewing files that changed from the base of the PR and between 2bcedc8 and b9a7c51.

📒 Files selected for processing (1)
  • ext/src/ext_algebra/massey.rs

📝 Walkthrough

Walkthrough

Refactors Massey product computation in ext/src/ext_algebra/massey.rs to remove the old per-generator null-homotopy path, add kernel-based third-factor validation and bracket construction, update massey_iter_c and massey, and extend regression tests for issue #116.

Changes

Massey product kernel refactor

Layer / File(s) Summary
Kernel-based bracket computation
ext/src/ext_algebra/massey.rs
Removes MasseyComputeDatum and adds massey_kernel and massey_bracket_of to compute valid third-factor kernels and realize a single bracket from an explicit class.
Wiring massey_iter_c and massey
ext/src/ext_algebra/massey.rs
massey_iter_c now enumerates kernel rows via massey_kernel and calls massey_bracket_of; massey(a,b,c) checks c·b == 0 with try_multiply before computing and returns None early otherwise.
Regression tests for issue #116
ext/src/ext_algebra/massey.rs
Adds a None-returning regression assertion for the non-kernel case and a new test that compares massey_iter_c against massey_iter_a after normalization.

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

Possibly related PRs

  • SpectralSequences/sseq#247: Introduces the earlier massey and massey_iter_{a,c} Massey-family APIs in the same area that this PR refactors.

Poem

I hop through kernels, neat and small,
And dodge the old null-homotopy thrall.
With b·c checked before the leap,
The Massey brackets now compute. 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: fixing a Massey product panic in proper multiplication kernels.
Linked Issues check ✅ Passed The changes address #116 by only lifting kernel classes, preventing the reported panic and adding regression tests.
Out of Scope Changes check ✅ Passed The patch stays focused on Massey product computation and regression tests, with no unrelated changes evident.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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 claude/fix-116

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.

`massey_representative`'s doc comment still linked `Self::massey_at`, which
was removed in this change. CI builds docs with broken intra-doc links
denied, so rustdoc failed. Point the doc at `massey_iter_a`, the remaining
user of the answers-matrix path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CAgVE4H22yxYTjSrH3r1iJ
claude added 2 commits July 3, 2026 01:11
Take `b_hom` by value (`Arc<...>`) instead of `&Arc<...>`: the function
hands it straight to `ChainHomotopy::new`, which consumes an owned `Arc`,
so ownership is what it actually needs — callers now clone at the call
site (a no-op move in the single-shot `massey`). Also fold the redundant
`offset_a` argument, which was fully derived from `a`, into the function
so `a` is self-sufficient. No behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CAgVE4H22yxYTjSrH3r1iJ
The `offset_a` binding introduced in the previous commit was formatted by
stable rustfmt; CI's lint job runs nightly rustfmt, which lays the method
chain out differently. Reformat so `just lint` passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CAgVE4H22yxYTjSrH3r1iJ
Comment on lines +94 to +96
if !resolution.has_computed_bidegree(prod_deg) {
return None;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We could also call compute_through_bidegree.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I kept the None here for consistency with the rest of the Ext API: multiply_into/try_multiply and massey_bracket_of all return None on an uncomputed bidegree rather than resolving as a side effect, and the massey example resolves up front via compute_through_stem. Auto-computing only here would also be half a fix unless massey_bracket_of did the same for tot, and inside massey_iter_c it would silently extend the resolution mid-sweep. Happy to switch to on-demand compute_through_bidegree if you'd rather make that a deliberate API choice across the product/Massey helpers.


Generated by Claude Code

Comment thread ext/src/ext_algebra/massey.rs Outdated
Per review: when the bracket bidegree `tot` is computed but has no
generators, the Massey product lands in the zero group, so it is the
(defined) zero element — not an undefined/uncomputed case. Return
`Some` with a zero representative instead of `None`, skipping the
null-homotopy. `None` now signals only an uncomputed `tot`.

This only affects the single-shot `massey`; `massey_iter_c` already
filters brackets containing zero, so its output is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CAgVE4H22yxYTjSrH3r1iJ
@JoeyBF
JoeyBF merged commit 75aa7a6 into master Jul 4, 2026
29 checks passed
@JoeyBF
JoeyBF deleted the claude/fix-116 branch July 4, 2026 18:29
github-actions Bot added a commit that referenced this pull request Jul 4, 2026
* Fix Massey product panic on proper multiplication kernels (#116)

`ExtAlgebra::massey_iter_c` and the single-shot `massey` built the
null-homotopy of `b ∘ c` per *generator* of the third-factor bidegree,
then computed the kernel of `·b` only afterwards. The null-homotopy of
`b ∘ gen_i` exists only when `b · gen_i = 0`, so whenever the kernel was
a proper subspace (some generator not individually killed by `b`) the
lift could not complete and `ChainHomotopy::extend` panicked ("Failed to
lift"). This surfaced for first factors of filtration `s >= 2`, e.g.
`<h3^2, d0, ->`.

Restructure both to realise the *actual* kernel class via
`ResolutionHomomorphism::from_class` and build a single valid
null-homotopy, mirroring the already-correct `massey_iter_a`:

- `massey_kernel` computes the valid third factors (kernel of `·b`) from
  the product maps alone, no homotopy.
- `massey_bracket_of` realises one kernel class and reads its bracket.
- `massey` now verifies `b · c = 0` up front and never lifts an invalid
  third factor.

This is no more expensive: `iter_c` now builds one homotopy per kernel
basis element (`dim(ker) <= num_gens`) instead of one per generator.
`massey_iter_a` is unchanged.

Adds regression tests: `<h1^2, h0, h0>` returns `None` without panicking,
and `massey_iter_c(h1^2, h0)` completes and agrees with `massey_iter_a`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CAgVE4H22yxYTjSrH3r1iJ

* Fix broken intra-doc link to removed massey_at

`massey_representative`'s doc comment still linked `Self::massey_at`, which
was removed in this change. CI builds docs with broken intra-doc links
denied, so rustdoc failed. Point the doc at `massey_iter_a`, the remaining
user of the answers-matrix path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CAgVE4H22yxYTjSrH3r1iJ

* Tidy massey_bracket_of signature

Take `b_hom` by value (`Arc<...>`) instead of `&Arc<...>`: the function
hands it straight to `ChainHomotopy::new`, which consumes an owned `Arc`,
so ownership is what it actually needs — callers now clone at the call
site (a no-op move in the single-shot `massey`). Also fold the redundant
`offset_a` argument, which was fully derived from `a`, into the function
so `a` is self-sufficient. No behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CAgVE4H22yxYTjSrH3r1iJ

* Apply nightly rustfmt to massey_bracket_of

The `offset_a` binding introduced in the previous commit was formatted by
stable rustfmt; CI's lint job runs nightly rustfmt, which lays the method
chain out differently. Reformat so `just lint` passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CAgVE4H22yxYTjSrH3r1iJ

* massey: treat an empty target bidegree as a defined zero bracket

Per review: when the bracket bidegree `tot` is computed but has no
generators, the Massey product lands in the zero group, so it is the
(defined) zero element — not an undefined/uncomputed case. Return
`Some` with a zero representative instead of `None`, skipping the
null-homotopy. `None` now signals only an uncomputed `tot`.

This only affects the single-shot `massey`; `massey_iter_c` already
filters brackets containing zero, so its output is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CAgVE4H22yxYTjSrH3r1iJ

---------

Co-authored-by: Claude <noreply@anthropic.com>
github-actions Bot added a commit to JoeyBF/sseq that referenced this pull request Jul 8, 2026
…quences#116) (SpectralSequences#258)

* Fix Massey product panic on proper multiplication kernels (SpectralSequences#116)

`ExtAlgebra::massey_iter_c` and the single-shot `massey` built the
null-homotopy of `b ∘ c` per *generator* of the third-factor bidegree,
then computed the kernel of `·b` only afterwards. The null-homotopy of
`b ∘ gen_i` exists only when `b · gen_i = 0`, so whenever the kernel was
a proper subspace (some generator not individually killed by `b`) the
lift could not complete and `ChainHomotopy::extend` panicked ("Failed to
lift"). This surfaced for first factors of filtration `s >= 2`, e.g.
`<h3^2, d0, ->`.

Restructure both to realise the *actual* kernel class via
`ResolutionHomomorphism::from_class` and build a single valid
null-homotopy, mirroring the already-correct `massey_iter_a`:

- `massey_kernel` computes the valid third factors (kernel of `·b`) from
  the product maps alone, no homotopy.
- `massey_bracket_of` realises one kernel class and reads its bracket.
- `massey` now verifies `b · c = 0` up front and never lifts an invalid
  third factor.

This is no more expensive: `iter_c` now builds one homotopy per kernel
basis element (`dim(ker) <= num_gens`) instead of one per generator.
`massey_iter_a` is unchanged.

Adds regression tests: `<h1^2, h0, h0>` returns `None` without panicking,
and `massey_iter_c(h1^2, h0)` completes and agrees with `massey_iter_a`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CAgVE4H22yxYTjSrH3r1iJ

* Fix broken intra-doc link to removed massey_at

`massey_representative`'s doc comment still linked `Self::massey_at`, which
was removed in this change. CI builds docs with broken intra-doc links
denied, so rustdoc failed. Point the doc at `massey_iter_a`, the remaining
user of the answers-matrix path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CAgVE4H22yxYTjSrH3r1iJ

* Tidy massey_bracket_of signature

Take `b_hom` by value (`Arc<...>`) instead of `&Arc<...>`: the function
hands it straight to `ChainHomotopy::new`, which consumes an owned `Arc`,
so ownership is what it actually needs — callers now clone at the call
site (a no-op move in the single-shot `massey`). Also fold the redundant
`offset_a` argument, which was fully derived from `a`, into the function
so `a` is self-sufficient. No behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CAgVE4H22yxYTjSrH3r1iJ

* Apply nightly rustfmt to massey_bracket_of

The `offset_a` binding introduced in the previous commit was formatted by
stable rustfmt; CI's lint job runs nightly rustfmt, which lays the method
chain out differently. Reformat so `just lint` passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CAgVE4H22yxYTjSrH3r1iJ

* massey: treat an empty target bidegree as a defined zero bracket

Per review: when the bracket bidegree `tot` is computed but has no
generators, the Massey product lands in the zero group, so it is the
(defined) zero element — not an undefined/uncomputed case. Return
`Some` with a zero representative instead of `None`, skipping the
null-homotopy. `None` now signals only an uncomputed `tot`.

This only affects the single-shot `massey`; `massey_iter_c` already
filters brackets containing zero, so its output is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CAgVE4H22yxYTjSrH3r1iJ

---------

Co-authored-by: Claude <noreply@anthropic.com>
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.

Failure to compute Massey products <h3^2, d0, ->

3 participants