ext_algebra: let the Ext DGA carry a differential and compute cohomology#267
ext_algebra: let the Ext DGA carry a differential and compute cohomology#267JoeyBF wants to merge 7 commits into
Conversation
`ExtAlgebra` models Ext as the cochain complex Hom(P•, k) = k^gens. Over a field with a minimal resolution its coboundary is identically zero, so Ext is just the generators. But a *deformation* (a motivic τ-Bockstein δ) or a *secondary* operation (an Adams d₂) makes the coboundary nonzero, and then the interesting object is its cohomology. This adds that differential as a pluggable piece: - `ExtDifferential`: a fixed bidegree `shift` plus, at each bidegree, the coboundary `matrix` in the generator bases. For graded coefficients (e.g. F₂[τ], graded by motivic weight) it also exposes `graded_dimension` / `matrix_capped`, so cohomology can be computed one graded slice at a time; the default (ungraded/field) makes those no-ops and recovers the old behavior exactly. - `ExtAlgebra::with_differential` / `differential` to attach and read it. - `cohomology_dimension` (+ `_capped`): dim H = gens − rank δ_out − rank δ_in. It's generic — nothing here is motivic. The two consumers are the motivic lift's δ and the secondary (d₂) machinery; the tests exercise it on the classical sphere with a mock differential (kernel/image bookkeeping) and confirm the zero-differential case is just the generators. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013ePtYD7Bt4iPeCtmqtqvZE
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughExtAlgebra now supports attached differentials and differential-aware cohomology. SecondaryExtAlgebra uses SecondaryCoboundary to derive E3 pages, while homomorphism reduction APIs receive per-bidegree E3 subquotient callbacks. ChangesExt differential integration
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant SecondaryExtAlgebra
participant ExtAlgebra
participant SecondaryCoboundary
participant SecondaryResolution
SecondaryExtAlgebra->>ExtAlgebra: attach SecondaryCoboundary
ExtAlgebra->>SecondaryCoboundary: request d2 matrix
SecondaryCoboundary->>SecondaryResolution: read homotopy data
SecondaryResolution-->>SecondaryCoboundary: provide matrix entries
SecondaryCoboundary-->>ExtAlgebra: compute E3 subquotient
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Validate that the differential→cohomology abstraction serves a second consumer: the Adams d₂. Two additions, no behavior change to existing paths. - `ExtAlgebra::cohomology_subquotient(b)`: the cohomology at `b` as a `Subquotient` of the generators — ker(δ out) mod im(δ into), i.e. actual representatives of the surviving classes, not just the dimension. The natural companion to `cohomology_dimension`; the motivic side wants it (surviving-class reps) as much as the secondary side does. - `SecondaryCoboundary: ExtDifferential`: the Adams d₂ presented as a coboundary on the primary ExtAlgebra, shift (n,s) → (n-1,s+2), matrix read from the secondary resolution's homotopies. Its `matrix` sizes each end at its own bidegree so an `a×0` (empty target: a d₂-cycle) and a `0×b` (off-axis source, in-quadrant target: an ambient-`b` image) stay distinct; off the first quadrant Ext is a *known* zero, in-quadrant-but-unresolved is unknown (`None`). Gate test: attaching `SecondaryCoboundary` and computing the E₃ page via the shared `cohomology_subquotient` reproduces the spectral-sequence bookkeeping's `page_data` exactly across the sphere in a range (dimension at every bidegree, including the axes). This is the groundwork for routing the secondary layer's page/product machinery onto the shared path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013ePtYD7Bt4iPeCtmqtqvZE
There was a problem hiding this comment.
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 `@ext/src/ext_algebra/mod.rs`:
- Around line 64-79: Document in the trait comments for graded_dimension and
matrix_capped that graded implementations must override both methods together,
ensuring the capped generator count matches the compacted capped matrix
dimensions. Make the invariant explicit near cohomology_dimension_capped so
future implementations do not override only graded_dimension.
- Around line 202-231: Validate the shapes of matrices returned by
ExtDifferential in cohomology_subquotient before using them: require d.matrix(b)
to have dim rows and d.matrix(source) to have dim columns. Return a clear
diagnostic through the method’s existing error/validation mechanism when either
contract is violated, including the bidegree and actual versus expected
dimensions, instead of allowing row or subspace construction to panic; preserve
the missing-matrix zero-image behavior.
- Around line 167-191: Update ExtAlgebra::cohomology_dimension_capped to
validate that rank_out plus rank_in does not exceed gens before performing the
usize subtraction. If the invariant is violated, return None (or the established
invalid-result representation) instead of allowing underflow; preserve the
existing dimension calculation for valid differentials.
- Around line 437-446: Update the mock ExtDifferential implementation’s matrix
method to return a zero matrix with dimensions matching the number of generators
in b, rather than always returning 0x0 outside Bidegree::n_s(0, 2). Preserve the
existing 1x1 matrix for that bidegree and use alg.dimension(b) or the
corresponding dimension source for the zero-matrix shape.
In `@ext/src/ext_algebra/secondary.rs`:
- Around line 41-47: Make SecondaryCoboundary constructible outside its defining
module by adding a public constructor that accepts the required resolution and
prime, or narrow the struct visibility to pub(crate) if it is internal. Also
remove the redundant p field and derive the prime from
res_lift.underlying().prime() wherever needed.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: f887ba1e-4ff7-4aa5-b4ac-a47d6183b1b5
📒 Files selected for processing (2)
ext/src/ext_algebra/mod.rsext/src/ext_algebra/secondary.rs
| pub fn cohomology_dimension(&self, b: Bidegree) -> Option<usize> { | ||
| self.cohomology_dimension_capped(b, i32::MAX) | ||
| } | ||
|
|
||
| /// The dimension of the DGA's cohomology at `b` restricted to the coefficient's | ||
| /// weight slice `≤ cap` — for a graded coefficient like $\mathbb{F}_2[\tau]$ | ||
| /// this is a slice of the Ext *module*, and sweeping `cap` exposes the | ||
| /// $\tau$-torsion (dimension above the free/`cap = ∞` rank). For an ungraded | ||
| /// (field) coefficient the differential reports no grading and this is just | ||
| /// [`cohomology_dimension`](Self::cohomology_dimension) for every `cap`. | ||
| pub fn cohomology_dimension_capped(&self, b: Bidegree, cap: i32) -> Option<usize> { | ||
| let Some(d) = &self.differential else { | ||
| return Some(self.dimension(b)); | ||
| }; | ||
| let gens = d | ||
| .graded_dimension(b, cap) | ||
| .unwrap_or_else(|| self.dimension(b)); | ||
| let shift = d.shift(); | ||
| let source = Bidegree::n_s(b.n() - shift.n(), b.s() - shift.s()); | ||
| let rank_out = d.matrix_capped(b, cap)?.row_reduce(); | ||
| let rank_in = d | ||
| .matrix_capped(source, cap) | ||
| .map_or(0, |mut m| m.row_reduce()); | ||
| Some(gens - rank_out - rank_in) | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win
Guard the gens - rank_out - rank_in computation against invariant violations.
This subtraction implicitly assumes rank_out + rank_in <= gens, which only holds if the differential satisfies d ∘ d = 0 at b. That's never checked, so a malformed or buggy ExtDifferential impl would trigger a usize underflow — panicking in debug or silently wrapping to a huge value in release — instead of a clear error.
🛡️ Proposed defensive check
let rank_out = d.matrix_capped(b, cap)?.row_reduce();
let rank_in = d
.matrix_capped(source, cap)
.map_or(0, |mut m| m.row_reduce());
+ debug_assert!(
+ rank_out + rank_in <= gens,
+ "ExtDifferential violates d∘d=0 at {b:?}: rank_out={rank_out}, rank_in={rank_in}, gens={gens}"
+ );
Some(gens - rank_out - rank_in)📝 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.
| pub fn cohomology_dimension(&self, b: Bidegree) -> Option<usize> { | |
| self.cohomology_dimension_capped(b, i32::MAX) | |
| } | |
| /// The dimension of the DGA's cohomology at `b` restricted to the coefficient's | |
| /// weight slice `≤ cap` — for a graded coefficient like $\mathbb{F}_2[\tau]$ | |
| /// this is a slice of the Ext *module*, and sweeping `cap` exposes the | |
| /// $\tau$-torsion (dimension above the free/`cap = ∞` rank). For an ungraded | |
| /// (field) coefficient the differential reports no grading and this is just | |
| /// [`cohomology_dimension`](Self::cohomology_dimension) for every `cap`. | |
| pub fn cohomology_dimension_capped(&self, b: Bidegree, cap: i32) -> Option<usize> { | |
| let Some(d) = &self.differential else { | |
| return Some(self.dimension(b)); | |
| }; | |
| let gens = d | |
| .graded_dimension(b, cap) | |
| .unwrap_or_else(|| self.dimension(b)); | |
| let shift = d.shift(); | |
| let source = Bidegree::n_s(b.n() - shift.n(), b.s() - shift.s()); | |
| let rank_out = d.matrix_capped(b, cap)?.row_reduce(); | |
| let rank_in = d | |
| .matrix_capped(source, cap) | |
| .map_or(0, |mut m| m.row_reduce()); | |
| Some(gens - rank_out - rank_in) | |
| } | |
| pub fn cohomology_dimension(&self, b: Bidegree) -> Option<usize> { | |
| self.cohomology_dimension_capped(b, i32::MAX) | |
| } | |
| /// The dimension of the DGA's cohomology at `b` restricted to the coefficient's | |
| /// weight slice `≤ cap` — for a graded coefficient like $\mathbb{F}_2[\tau]$ | |
| /// this is a slice of the Ext *module*, and sweeping `cap` exposes the | |
| /// $\tau$-torsion (dimension above the free/`cap = ∞` rank). For an ungraded | |
| /// (field) coefficient the differential reports no grading and this is just | |
| /// [`cohomology_dimension`](Self::cohomology_dimension) for every `cap`. | |
| pub fn cohomology_dimension_capped(&self, b: Bidegree, cap: i32) -> Option<usize> { | |
| let Some(d) = &self.differential else { | |
| return Some(self.dimension(b)); | |
| }; | |
| let gens = d | |
| .graded_dimension(b, cap) | |
| .unwrap_or_else(|| self.dimension(b)); | |
| let shift = d.shift(); | |
| let source = Bidegree::n_s(b.n() - shift.n(), b.s() - shift.s()); | |
| let rank_out = d.matrix_capped(b, cap)?.row_reduce(); | |
| let rank_in = d | |
| .matrix_capped(source, cap) | |
| .map_or(0, |mut m| m.row_reduce()); | |
| debug_assert!( | |
| rank_out + rank_in <= gens, | |
| "ExtDifferential violates d∘d=0 at {b:?}: rank_out={rank_out}, rank_in={rank_in}, gens={gens}" | |
| ); | |
| Some(gens - rank_out - rank_in) | |
| } |
🤖 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 `@ext/src/ext_algebra/mod.rs` around lines 167 - 191, Update
ExtAlgebra::cohomology_dimension_capped to validate that rank_out plus rank_in
does not exceed gens before performing the usize subtraction. If the invariant
is violated, return None (or the established invalid-result representation)
instead of allowing underflow; preserve the existing dimension calculation for
valid differentials.
…y path The secondary layer computed its E₃ pages by building a whole Sseq<2, Adams> per resolution and reading `page_data` off it. Now that the Adams d₂ is an `ExtDifferential`, `cohomology_subquotient` computes the same E₃ page directly (ker d₂ / im d₂), so the parallel bookkeeping is redundant. - `ExtAlgebra::cohomology_subquotient(b)`: the E₃ page (representatives, not just the dimension `cohomology_dimension` gives). - `SecondaryExtAlgebra` holds an Ext-with-d₂ for `M` and for the unit; its `page_data`/`unit_page_data` are now `cohomology_subquotient`, and `extend_all` no longer builds any Sseq. The `res_sseq`/`unit_sseq` fields, their construction, and the `e3_page_data` helper are gone. - `SecondaryResolutionHomomorphism::hom_k` took a whole `Sseq` only to read one page (the λ-part's d₂-image quotient). It now takes the E₃ page as a function of bidegree and queries it at its own `lambda_source` — no fragile reconstruction of that bidegree by the caller (the first, index-arithmetic attempt is what the new product smoke test caught). Hardening from review: - assert `ExtDifferential::matrix` shapes in `cohomology_subquotient` (gens(b) rows / gens(b) columns), so a nonconformant impl fails loudly; - debug_assert `rank_out + rank_in ≤ gens` before the cohomology subtraction; - document that graded impls must override `graded_dimension`/`matrix_capped` together; - the `MockDiff` test fixture now returns correctly-shaped zero matrices (via the resolution's generator counts), making it a valid reference for `cohomology_subquotient`, not just rank-only `cohomology_dimension`; - `SecondaryCoboundary` is `pub(crate)` and derives its prime from the resolution instead of storing it. Validated: `secondary-S_2` and `secondary-C2` benchmarks are byte-identical to golden; new tests cover the E₃-page agreement, the mock subquotient, and that the secondary product's Ext part is the primary product. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013ePtYD7Bt4iPeCtmqtqvZE
… + nightly fmt The `hom_k`/`hom_k_with` signature change (Sseq → E₃-page function) missed a caller: `examples/secondary_massey.rs`, which only compiles under default features (my local runs used --no-default-features, and my grep for callers searched src/ only). Adapt its call to pass the E₃ page as a closure over the example's existing `get_page_data`. Also run nightly `cargo fmt` (the repo's rustfmt.toml enables unstable options — reorder_impl_items, group_imports, format_strings — that stable rustfmt silently skips), which the `lint` CI job checks. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013ePtYD7Bt4iPeCtmqtqvZE
…lved range `secondary_product`/`secondary_massey` query the E3 page one row past the resolved filtration, where the outgoing d2 lands at an unresolved bidegree. The old Sseq path (SecondaryResolution::e3_page) treats an uncomputed outgoing differential as zero — those classes provisionally survive — so page_data was always defined there. cohomology_subquotient is stricter: a None outgoing matrix makes it return None, and unit_page_data's .expect() then panicked (caught by the C2 v_1 benchmark under `just benchmarks`). Reproduce the e3_page convention locally in SecondaryCoboundary::matrix by distinguishing the two ends: an in-quadrant-but-unresolved *source* still means the page is unknown (None), but an in-quadrant-but-unresolved *target* means the secondary resolution records no d2 there yet, so give a `rows × 0` matrix (the whole source is a provisional d2-cycle) instead of None. The generic cohomology_subquotient contract is unchanged. That `rows × 0` matrix is never consumed as an incoming differential, since the target bidegree's own subquotient short-circuits to None at its numerator. Verified byte-identical against the secondary-*, secondary_product-*, and secondary_massey-* golden benchmarks. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013ePtYD7Bt4iPeCtmqtqvZE
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
ext/src/ext_algebra/mod.rs (1)
188-196: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winValidate capped matrix dimensions before using their ranks.
An undersized capped matrix can satisfy the rank assertion while silently overstating cohomology. Assert that the outgoing matrix has
gensrows and the incoming matrix hasgenscolumns before reduction.Proposed fix
- let rank_out = d.matrix_capped(b, cap)?.row_reduce(); - let rank_in = d - .matrix_capped(source, cap) - .map_or(0, |mut m| m.row_reduce()); + let mut out = d.matrix_capped(b, cap)?; + assert_eq!(out.rows(), gens); + let rank_out = out.row_reduce(); + let rank_in = match d.matrix_capped(source, cap) { + Some(mut incoming) => { + assert_eq!(incoming.columns(), gens); + incoming.row_reduce() + } + None => 0, + };🤖 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 `@ext/src/ext_algebra/mod.rs` around lines 188 - 196, In the rank computation around graded_dimension, validate matrix dimensions before row reduction: ensure the outgoing matrix from matrix_capped(b, cap) has gens rows and the incoming matrix from matrix_capped(source, cap) has gens columns. Preserve the existing rank defaults and error propagation while asserting these dimensions before calling row_reduce.
🤖 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.
Outside diff comments:
In `@ext/src/ext_algebra/mod.rs`:
- Around line 188-196: In the rank computation around graded_dimension, validate
matrix dimensions before row reduction: ensure the outgoing matrix from
matrix_capped(b, cap) has gens rows and the incoming matrix from
matrix_capped(source, cap) has gens columns. Preserve the existing rank defaults
and error propagation while asserting these dimensions before calling
row_reduce.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: f196e61e-c144-4759-b08b-d2a399f8f695
📒 Files selected for processing (4)
ext/examples/secondary_massey.rsext/src/ext_algebra/mod.rsext/src/ext_algebra/secondary.rsext/src/resolution_homomorphism.rs
cohomology_dimension_capped mixed a capped generator count (graded_dimension) with ranks read off matrix_capped without checking the matrices are the right shape. An undersized capped matrix would understate a rank and silently overstate the cohomology. Assert the outgoing matrix has `gens` rows and the incoming matrix has `gens` columns before row-reducing, matching the shape checks already in cohomology_subquotient. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013ePtYD7Bt4iPeCtmqtqvZE
Lead ExtAlgebra and ExtDifferential with the classical story — Ext is the cohomology of Hom(P•,k); minimal resolution ⇒ generators, non-minimal ⇒ the canonical dualised differential Hom(d,k) — rather than the deformation story. The Adams d2 / motivic δ are named only as *also* using the pluggable coboundary, with the machinery kept in their own modules (the secondary layer); they no longer headline ExtAlgebra's interface. Generalize graded_dimension's wording from "motivic weight" to "a deformation base R" (F2[τ] as the example), since the cap-sweep is the general R-module shape, not motivic-specific. Docs only — no logic changes. Docs build clean under -D broken_intra_doc_links -D private_intra_doc_links. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013ePtYD7Bt4iPeCtmqtqvZE
A small, generic enhancement to
ExtAlgebra, independent of the motivic work (it shares no files with it and can merge on its own). It's one of the foundations of the C-motivic Adams E₂ deformation effort, split out so it can be reviewed on its own terms.Motivation
ExtAlgebramodels Ext as the cochain complex Hom(P•, k) = k^gens. Over a field with a minimal resolution the coboundary is identically zero — dₛ lands in Ā·P_{s−1}, which every φ: P_{s−1} → k kills — so Ext is just the generators and "taking cohomology" is a no-op. But a deformation (a motivic τ-Bockstein δ) or a secondary operation (an Adams d₂) makes the coboundary nonzero, and then the object of interest is its cohomology.What it adds
ExtDifferential— a pluggable DGA differential: a fixed bidegreeshift, and at each bidegree the coboundarymatrixin the generator bases. For graded coefficients (e.g. F₂[τ], graded by motivic weight) it also exposesgraded_dimension/matrix_capped, so the cohomology can be computed one graded slice at a time. The default impls make those ungraded no-ops, so the field/minimal case behaves exactly as before.ExtAlgebra::with_differential/differential— attach and read the differential.cohomology_dimension(+_capped) —dim H = gens − rank δ_out − rank δ_in.Scope
Nothing here is motivic. The two intended consumers are the motivic lift's δ (a later PR) and the existing secondary (d₂) machinery. Tests exercise it on the classical sphere with a mock differential — confirming the kernel/image bookkeeping, and that a zero differential recovers "cohomology = generators".
cargo test -p ext --lib ext_algebra— 7 passing.🤖 Generated with Claude Code
Generated by Claude Code
Summary by CodeRabbit