feat(Tensors/Contraction): slot-addressed cross contraction and the general-rank raise/lower round trip - #1375
Conversation
|
Thank you for this PR, which will now be reviewed. If submitting to ./Physlib or ./QuantumInfo, please see our review guidelines if you are not familiar with the process. You should expect a back and forth with a reviewer before your PR is merged. See also that link for how to add appropriate labels to your PR. The PR will also go through a number of automated checks. You can learn more about these here, including how to run them locally. If you are submitting to ./PhyslibAlpha there will be a lighter review process, though your PR must still pass the automated checks. If you want to bring attention to this PR, please write a message on this thread of the Lean Zulip. Important: If a reviewer adds an |
nateabr
left a comment
There was a problem hiding this comment.
Hi I had a look through your work and it looks great! I just had a few comments that might help.
I think in general it might be worth considering that these are foundational files and will probably be used as we build up more physics in relativity so compilation times would be a good thing to think about as we scale :)
|
also upon reading my comments I believe have come across a little rude, I apologize for this :( awaiting-author |
Not rude at all! I am already working on them. Thank you very much for such a quick feedback! |
| except its right bond (`cA ∘ Fin.castSucc`) followed by those of `t2` except its left bond | ||
| (`cB ∘ Fin.succ`); the result is again bond-distinguished `Fin (1 + (nA + nB) + 1)`, so | ||
| cross-contractions chain without recasts. -/ | ||
| noncomputable def contrProdT {nA nB : ℕ} {cA : Fin (1 + nA + 1) → C} {cB : Fin (1 + nB + 1) → C} |
There was a problem hiding this comment.
Would this work with just (nA +1) and (nB + 1), I don't see why you need the additional 1+.
There was a problem hiding this comment.
Is it worth naming this bondT (or just bond then?)
|
To ask a more general question: What is does this give you which normal tensor contraction doesn't. For example |
|
@jstoobysmith even i was thinking about this and you are right that this doesn't have an index notation anologue, but maybe its similar to operator algebra? But moreover practically, the use case that jumped out to me is if we need to chain many contractions together of tensors of different rank, leans system might fight against us and we probably wont be able to use However, I'm not a 100% sure, maybe there's some lean metaprogramming that can help us here? |
|
@jstoobysmith You're right that for a single contraction the index notation already wins: Aⁱⱼ Bʲₖ is just {A | i j ⊗ B | j k}ᵀ. The bond fixes a standard slot layout for every tensor: the two indices used for contracting always sit at fixed positions, first slot and last slot, with everything else in the middle. The contraction operation is defined so that its output is put back into this same standard layout. So the result of one contraction is already in exactly the form the next contraction expects, and no correction is needed between steps. The output of a contraction is again a valid input. That said, I fully agree it's not ideal that this has no index-notation analogue. I'm currently exploring whether the current physlib machinery can be extended to solve this chaining problem. |
|
Actually, I think it does have an index notation analog! It's just something akin to mulVec e.g. we could write etc. Where does the name |
|
The name comes from tensor networks (https://tensornetwork.org/mps/). I adopted it somewhat loosely, as I am not familiar with tensor network theory. If we consider tensors that are represented by matrices A | i j and B | j k, contracting the last index of A and the first of B is just the row by column product. |
| refine ⟨Function.bijective_id, fun k => ?_⟩ | ||
| simp only [id_eq, Function.comp_apply] | ||
| obtain ⟨k, rfl⟩ := finSumFinEquiv.surjective k | ||
| match k with | ||
| | Sum.inl i => | ||
| rw [finSumFinEquiv_apply_left, | ||
| show Fin.cast (by omega) (Fin.succSuccAbove ⟨1 + nA, by omega⟩ ⟨1 + nA + 1, by omega⟩ | ||
| (Fin.castAdd (1 + nB) i)) = Fin.castAdd (1 + nB + 1) (Fin.castSucc i) from by | ||
| have := i.isLt | ||
| simp only [Fin.ext_iff, Fin.val_cast, Fin.succSuccAbove_val, Fin.val_castAdd, | ||
| Fin.val_castSucc]; split_ifs <;> omega] | ||
| simp [Fin.append_left] | ||
| | Sum.inr i => | ||
| rw [finSumFinEquiv_apply_right, | ||
| show Fin.cast (by omega) (Fin.succSuccAbove ⟨1 + nA, by omega⟩ ⟨1 + nA + 1, by omega⟩ | ||
| (Fin.natAdd (1 + nA) i)) = Fin.natAdd (1 + nA + 1) (Fin.succ i) from by | ||
| have := i.isLt | ||
| simp only [Fin.ext_iff, Fin.val_cast, Fin.succSuccAbove_val, Fin.val_natAdd, | ||
| Fin.val_succ]; split_ifs <;> omega] | ||
| simp [Fin.append_right]) |
There was a problem hiding this comment.
Maybe worth extracting and putting into Reindexing
There was a problem hiding this comment.
Likewise with the other large proofs in permT's.
| except its right bond (`cA ∘ Fin.castSucc`) followed by those of `t2` except its left bond | ||
| (`cB ∘ Fin.succ`); the result is again bond-distinguished `Fin (1 + (nA + nB) + 1)`, so | ||
| cross-contractions chain without recasts. -/ | ||
| noncomputable def contrProdT {nA nB : ℕ} {cA : Fin (1 + nA + 1) → C} {cB : Fin (1 + nB + 1) → C} |
There was a problem hiding this comment.
Is it worth naming this bondT (or just bond then?)
| clean size `n` (with `h : n1 = n`), by transporting the tensor and the two contracted indices | ||
| along the canonical `Fin.cast`. This exposes the intermediate contraction count in `n + 1 + 1` | ||
| form so that `contrT_permT` and `contrT_comm` can fire. -/ | ||
| lemma contrT_cast {n n1 : ℕ} {c : Fin (n1 + 1 + 1) → C} |
There was a problem hiding this comment.
This I think already exists, though if it doesn't this file is not the right place.
| cross-contractions chain without recasts. -/ | ||
| noncomputable def contrProdT {nA nB : ℕ} {cA : Fin (1 + nA + 1) → C} {cB : Fin (1 + nB + 1) → C} | ||
| (hc : S.τ (cA (Fin.last (1 + nA))) = cB 0) (t1 : Tensor S cA) (t2 : Tensor S cB) : | ||
| Tensor S (Fin.append (cA ∘ Fin.castSucc) (cB ∘ Fin.succ) ∘ |
There was a problem hiding this comment.
Lift to a bilinear map? Then you don't need all of the proofs below.
|
|
||
| - `contrProdT` — two-bond · two-bond → two-bond: `Fin (1 + nA + 1)` · `Fin (1 + nB + 1)` → | ||
| `Fin (1 + (nA + nB) + 1)`, the operator product `(A·B)ⁱₖ = Aⁱⱼ Bʲₖ`; | ||
| - `contrVecProdT` — vector · two-bond → vector: `Fin (nV + 1)` · `Fin (1 + nM + 1)` → |
There was a problem hiding this comment.
I'm slightly confused about the difference between these. But maybe I can ask, will we only ever use these for nA, nB, nV and nM = 0? or are there application when this is not the case?
If the former, I think the Api would be simplified a lot by making this restriction (and it would let us proof a lot more about these things I think).
| as plain iterated contraction, no cast at any step — this closure is why the bond layout exists, | ||
| turning matrix-chain manipulation from a tower of transports into definitional rewriting. | ||
|
|
||
| The machinery multiplies tensors of two bond shapes: a *two-bond* tensor `Fin (1 + n + 1)` (a left |
There was a problem hiding this comment.
Also, I think we can make these doc-strings less verbose as well (I don't think AI does a particularly good job at writing these).
There was a problem hiding this comment.
Yes that is true.
|
I am currently writing a crosscontraction file which is just contrT and prodT on a pair of tensors where we prove associativity and other basic properties. This is very useful when we want to raise and lower indices with metric tensors. Example: Bond becomes a specific case where we have a chain of tensors contracted at extremal indices such as a chain of lorentz transformations. Ideally, we would have a general rank tensor algebra that does not need Fin.cast at every step. This turns out to be very challenging. I have not replied to the comments yet because I am still exploring a few possibilities. |
|
Sounds good! I think you already know this, but just in case not: this (which is now merged with toDualAtIndex) #1348 allows you to rise and lower indices more easily, with the index notation. |
2684ee6 to
8c6bffd
Compare
RescopingI've replaced the PR description with a rescoped version (see the updated body). Original scope. The goal was a general-rank tensor algebra to solve the New scope. I've cut the PR down to exactly what my SUSY (N=1 Kähler) work needs, and no more. SummaryThe PR adds
The general-rank named-index dual ( What this does not solve
Naming the operation does not remove the count cast, but it is still worthwhile.
So |
|
will get to this @pariandrea if Joseph doesn't beat me to it, please bare with me Thanks for your work :)) |
|
Could you give an example here of where you want to use |
Maybe I'm asking here: what is your exact use case for these functions. Above you write: What is the situation which index notation does not cover concretely? Is it something which goes beyond index notion even on pen and paper, or is it something which is a limitation of the index notation in Physlib. |
|
I have rescoped the branch and rewritten the description. (t·g)·g⁻¹ = t·(g·g⁻¹) for a general rank tensor t. The round-trip lemma: lemma crossContrT_crossContrT_of_unit_at {nA : ℕ} {c : Fin (nA + 1) → C} {d : C} For the concrete rank-one Kähler application index notation can describe the diagram, e.g. schematically {V | I ⊗ g | I J̄}ᵀ, but that elaborates to raw contrT (prodT …) at a non-canonical colour, and the round trip still has to be proved against that term. The cost of doing this per fixed rank is visible on master: toDualMap_fromDualMap is a ~35-line transport computation needing respectTransparency false, for rank 1 and slot 0 only. Each fixed-rank instance reproduces that grind with its own transports. My logic is to give a cross contraction a canonical end form: survivor indices of A followed by survivor indices of B, relabelled without gaps. However, I am unsure crossContrT is the right abstraction. This canonical ordering always puts the replacement index in the last slot, whatever slot was contracted: contracting slot i = 1 of [c₀, c₁, c₂, c₃] against a rank-2 M = [τ(c₁), d] gives [c₀, c₂, c₃, d], not [c₀, d, c₂, c₃]. That ordering is what makes crossContrT_chain_assoc statable at general rank: appending survivors on the right is itself associative, so both bracketings produce the same survivor list in the same order. But raising and lowering needs the index in place. It wants the result colour Function.update c i d: slot i relabelled, nothing moved, as in T^μ_νρ ↦ T_μνρ. This is the type of contrAtSlot, and the one the Kähler raise/lower and toDualAtIndex need. So I proved associativity in the appended-survivor convention and the raise/lower round trip in the in-place convention, and the friction between the two is where the remaining complexity lives: contrAtSlot is crossContrT plus the cycle rotating d back to slot i, and that rotation resurfaces in every lemma connecting the two layers. Honestly, I have spent a long time trying to come up with something better and I might need some guidance. Otherwise, we can also postpone this and work at fixed rank until a better design comes up. |
|
Opps: commented this before I saw your reply Sorry for the noise, but let me answer my own question here. I think the real motivation for defining this general construction is that it is often the case that we have some special tensors in a theory, for example, the unit tensors, the metric tensors, or the Kahler metric tensors. In general, we want to contract those tensors and specific indices of those tensors with specific indices of other, often arbitrary, tensors. The mechanism that's in this pull request is what gives us the framework to do that. You could use the definitions we have already to do this. However, because these sorts of manipulations are so common, it is useful to have a general way to construct these index contractions. A specific example is the toDualIndexAt, which now uses this function as a base. This is really one of the main motivations behind defining it: it allows you to define toDualIndexAt, but also other things which are similar. If this is correct, I think we need to include this in the doc string and consider how we can frame things around this, because this is the clear motivation behind this. I don't think the motivation about needing to contract to indices of two different tensors is as strong. |
If we instead focus on abstracting |
|
Yes, contracting the special tensors of a theory (unit, metric, Kähler metric) against named slots of arbitrary tensors is the right description of the use case, and I'll lead the docstrings with it. One precision: the theorems are one level more general than the metric examples. The round trip is proved once, for any matched pair of rank-2 tensors that collapses to the unit tensor (crossContrT_crossContrT_of_unit_at), at any colour pair, any rank, any slot. Example of crossContrT_crossContrT_of_unit_at is T^{μνρ} (M₁)_{νσ} (M₂)^{στ} = δ_ν^τ T^{μνρ} = T^{μτρ} The species metric (toDualAtIndex_toDualAtIndex) and the Kähler metric (raise/lower, where the two slots have different colours, the reason d is free in contrAtSlot) are instances. Any future metric-like pairing gets its round trip from the same lemma. That generality comes from crossContrT_chain_assoc: associativity of a contraction chain, (A·B)·C = A·(B·C). The two bracketings land at differently grouped types, (nA + nB) + nC vs nA + (nB + nC), but since crossContrT appends survivors on the right, both sides produce the same survivor list in the same order, so the lemma only needs the regrouping cast, at any rank, any slot. The round trip runs on this: raising then lowering is (t·g)·g⁻¹, and g·g⁻¹ = δ can't fire until you regroup to t·(g·g⁻¹). On "you could use the definitions we have already", at fixed rank, yes: that's what toDualMap_fromDualMap on master does, ~35 lines of transports doing that regrouping by hand, rank 1 slot 0 only (and toDualAtIndex has no lemmas at all). At general rank the raw bracketings sit at non-canonical colours and the reassociation isn't statable as one lemma; the canonical output colour is what turns it into a cast. The issue with general rank crossContrT_chain_assoc is that it forces the following two conventions: Convention 1 — survivors appended (what crossContrT does). Output = A's surviving slots, then B's surviving slots, gaps closed. The new index always ends up last: contract slot 1 of [c₀, c₁, c₂, c₃] against [τc₁, d] → [c₀, c₂, c₃, d] Convention 2 — index in place (what contrAtSlot exposes). Output = same layout as the input, only the contracted slot's colour changes: contract slot 1 of [c₀, c₁, c₂, c₃] against [τc₁, d] → [c₀, d, c₂, c₃] So this framework looks messy because for the associativity we need the first convention and for raise/lower we need the second. |
|
Is there an easy way to see why associatively forces the first convention? |
|
I do not think that it is impossible to prove without the convention. Convention 1 Convention 2 If we use the physically better convention 2 the colour position will depend on the indices we contract. |
|
I think convention 2 only works anyway in the case where one of the tensors is of rank 2, maybe this assumption is enough to simplify the calculation enough? |
|
Fixing the metric to rank 2 doesn't simplify Following your point that the abstraction is contracting special tensors at named slots, I've reframed the API so the names carry the design. No behaviour change beyond the renamed identifiers. The two conventionsEvery slot contraction has to decide where the surviving index lands, and the names now say which.
The connecting lemmaThe single place the two conventions meet: the cycle that walks the result from the end back to slot Structure
What consumes this downstreamThe N=1 Kähler layer is the live consumer, exercising exactly the generality the species metric does not:
|
cae0313 to
3bab643
Compare
| `h : n1 = n`, by transporting the tensor and the two contracted indices along the canonical | ||
| `Fin.cast`. Exposes the contraction count in `n + 1 + 1` form so `contrT_permT` and `contrT_comm` | ||
| can fire. -/ | ||
| lemma contrT_cast {n n1 : ℕ} {c : Fin (n1 + 1 + 1) → C} |
There was a problem hiding this comment.
How is this different from contrT_congr?
There was a problem hiding this comment.
contrT_cast` deleted; the file is back to its master content, untouched by this PR
There was a problem hiding this comment.
What is the motivation for moving these results? I don't think ./Contraction/... is the best place for them
There was a problem hiding this comment.
the results are back in Dual.lean
| private lemma pure_cast_congr {n : ℕ} {c : Fin n → C} (p : Pure S c) {a b : Fin n} (hab : a = b) | ||
| {W : C} {h1 : c a = W} {h2 : c b = W} : | ||
| LinearEquiv.cast (R := k) h1 (p a) = LinearEquiv.cast (R := k) h2 (p b) := | ||
| Pure.congr_mid W p a b hab h1 h2 |
There was a problem hiding this comment.
Should not do this, as one can just use Pure.congr_mid
There was a problem hiding this comment.
Don't need lemmas like this
There was a problem hiding this comment.
pure_cast_congr deleted; Pure.congr_mid used directly
| `h : n1 = n`, by transporting the tensor and the two contracted indices along the canonical | ||
| `Fin.cast`. Exposes the contraction count in `n + 1 + 1` form so `contrT_permT` and `contrT_comm` | ||
| can fire. -/ | ||
| lemma contrT_cast {n n1 : ℕ} {c : Fin (n1 + 1 + 1) → C} |
There was a problem hiding this comment.
My comment here still holds, that I don't see the difference between this and contrT_congr.
| /-- `contrPCoeff` is insensitive to the proof arguments once the two contracted slots agree. | ||
| Keeps coefficient comparisons in `crossToEnd_chain_assoc` from invoking a broad `congr` search | ||
| through the contraction coefficient's proof fields. -/ | ||
| private lemma contrPCoeff_congr {n : ℕ} {c : Fin n → C} (p : Pure S c) |
There was a problem hiding this comment.
Should move to file where contrPCoeff and not made private
There was a problem hiding this comment.
public, in Contraction/Pure.lean beside contrPCoeff
|
awaiting-author |
…dressed contraction The finite-index groundwork for contracting a named slot of one tensor against a named slot of another. `SuccSuccAbove.lean` gains `Fin.reinsert_castAdd` and `Fin.reinsert_natAdd`: reinserting a surviving slot after removing one slot from each block of `Fin ((nA + 1) + (nB + 1))`. Unlike the neighbouring `succSuccAbove_natAdd_apply_castAdd`, the two removed slots straddle the two blocks, which is why the statements carry the reshaping `Fin.cast`s. `Reindexing.lean` gains a `Fin` section for the `Fin.append` and `Fin.succAbove` maps its reindexings are built from: the block-swap and block-reassembly value lemmas, `append_succAbove_const_eq_cycleIcc` and `cycleIcc_last_symm_apply_succAbove` identifying "move slot `i` to the end" with the cycle `[i, last]`, `exists_succAbove_comm` restricting an injection to the survivors of a slot, and two lemmas pinning a reshaped slot to its literal value. `Fin.append_castAdd_cast` and `Fin.append_natAdd_cast`, reassembling the blocks after casting one of them, are stated as equalities of index maps rather than of `.val`, which is the underlying fact; the `.val` form follows from `Fin.val_cast`. At the `IsReindexing` level it gains `succAbove_of_succAbove_eq`, the survivor reindexing obtained from a caller-supplied `Fin.succAbove` square rather than from `σ` as in `succAbove`, and `moveLast`, which splits a color list at an arbitrary slot and generalises `append_succ_last`. The `_of_` names the hypothesis the lemma takes rather than a case of the trichotomy `succAbove` unifies, and `_eq` rather than `_comm`, which this file reserves for lemmas whose conclusion is that two index-map operations commute. `Tensor.permT_injective` records that a reindexing is injective. Co-authored-by: Claude Opus 4.8 <no-reply+claude-opus-4-8@anthropic.com>
`Pure.contrPCoeff_congr` says the contraction coefficient is insensitive to its proof arguments once the two contracted slots agree, the pure-level mirror of `contrT_congr`. `Pure.permP_dropPair_dropPair_congr` and `Pure.permP_dropPair_permP_congr` compare two towers of `permP` and `dropPair` over a common pure tensor: they agree as soon as the composite slot maps agree pointwise. Stating the comparison this way keeps every slot map an opaque variable, so it closes by syntactic unification rather than by `whnf`-reducing the color and index-map composites, which is what makes the rank-two chain calculations tractable. `prodT_contrT_fst` contracts two slots of the first factor of a product, the mirror of the existing `prodT_contrT_snd`. Co-authored-by: Claude Opus 4.8 <no-reply+claude-opus-4-8@anthropic.com>
…action `contrT` contracts two slots of a single tensor. Contracting a slot of one tensor against a slot of another, which is what pairing a tensor with one of a theory's special tensors amounts to, means forming the product by hand, tracking where each slot lands, and discharging the distinctness and duality goals at every call site. `crossToEnd i j hc` packages that: `prodT` then `contrT`, with the wrapper locating the two post-product slots, discharging the proofs, and normalising the output color to `Fin.append (cA ∘ i.succAbove) (cB ∘ j.succAbove)` — left survivors, then right, gaps closed — so a chain composes without hand-inserted recasts. The slots are named arguments, so any slot, interior included, is reached directly. Proved on it: the value at concrete rank two (`crossToEnd_two`), equivariance, the two `permT` commutators moving a relabelling of either factor through the contraction, and `crossToEnd_assoc_rankTwo`, which rebrackets the metric chain `A —(iA·0)— B —(last·0)— C` at rank-two `B` and `C` up to `permT id`. That regrouping is what lets a metric and its inverse be collapsed first; it is not associativity in general. Its surviving-slot identity pins the reshaped slots to their literals so that `succSuccAbove_val` fires, then closes the nested reinsertion conditionals with `grind (splits := 20)`, the tactic `SuccSuccAbove.lean` already uses for this shape of goal. Co-authored-by: Claude Opus 4.8 <no-reply+claude-opus-4-8@anthropic.com>
`crossToEnd` deposits the surviving index at the end of the survivor list.
Raising and lowering a named index wants it to stay in place: contracting slot
`i` of `t` against a rank-two tensor `M` should return `t` with only slot `i`'s
color changed, `T^{μ}{}_{νρ} ↦ T_{μνρ}`.
`crossToSlot i j hc M` is that operation, `crossToEnd` followed by the cycle
that walks the survivor from the last slot back to `i`.
`crossToSlot_eq_crossToEnd` is the single place the two conventions meet. The
contracted color is a propositional argument, so a rank-two tensor whose slot
color matches only up to an equality enters with no transporting `permT`.
The module stays at the `CommRing` altitude of `crossToEnd`; the `RCLike` round
trips built on it live with the unit-tensor theory.
Co-authored-by: Claude Opus 4.8 <no-reply+claude-opus-4-8@anthropic.com>
…ound trip The unit tensor is the identity for slot contraction: contracting any slot of `t` against `unitTensor` for that slot's color returns `t`, the slot carried to the survivor tail. `crossToEnd_unitTensor` proves this at any named slot, from the boundary case by spectator decomposition along the last slot. From it, `crossToSlot_raise_lower_round_trip`: for a rank-two `M` and an inverse `M'` whose shared index contracts away to the unit tensor, contracting slot `i` with `M` and then with `M'` is the identity up to the `Function.update` color reindex, at any rank and any slot. Every color is threaded propositionally, so a caller whose metric and inverse sit at literal colors supplies them directly and recovers the tensor with slot `i` at the literal, with no transporting `permT` on either factor. The result-to-end form `crossToEnd_round_trip_of_unit_slot` is stated separately, since it is what the chain regrouping consumes. Because `unitTensor` is `RCLike`-valued, this material sits over an `[RCLike k]` variable block, apart from the `CommRing` `crossToEnd`/`crossToSlot` algebra. Co-authored-by: Claude Opus 4.8 <no-reply+claude-opus-4-8@anthropic.com>
… every rank `toDualMapAtIndex` is now `crossToSlot` against `metricTensor (τ (c i))`, the case where the rank-two tensor's two slots share a color, and `toDualMapAtIndex_toDualMapAtIndex` states the double-dual round trip at every rank and every named slot, as the same-color instance of `crossToSlot_raise_lower_round_trip`. That subsumes the rank-one slot-zero maps `toDualMap`, `fromDualMap`, `toDual` and their four round trips, which are removed. Their round trip had needed a bespoke transport computation under `backward.isDefEq.respectTransparency false`; the general-rank proof needs no transparency override. It also retires `IsReindexing.contr_two_rotate`, whose only purpose was to rotate the old `toDualAtIndex`, so the lemma is dropped here rather than left in `Reindexing` without a consumer. `fromDualMapAtIndex` absorbs the color cast the two `Function.update`s generate, which is what makes the round trips cast-free in both directions, and `toDualAtIndex` bundles the pair as a `LinearEquiv`. Its equivariance is `toDualMapAtIndex_equivariant`, which is `@[simp]`; restating it at the bundled type would add nothing, since the two maps are definitionally equal. `Elab.lean` follows the rename in the `jiggle` operator, so the `τ` index notation elaborates to `toDualMapAtIndex`. Co-authored-by: Claude Opus 4.8 <no-reply+claude-opus-4-8@anthropic.com>
…quivalence `crossToSlot_raise_lower_round_trip` showed that contracting slot `i` against `M` and then against `M'` is the identity when the pair collapses to the unit tensor. Nothing bundled that with the reverse collapse, so the only mutually inverse pair in the library was the metric, which `toDualAtIndex` inverted by hand. `crossToSlotEquiv` is the general statement: a pair collapsing to the unit tensor in both orders makes the two contractions mutually inverse, so `c` and `Function.update c i d` carry the same tensors. The surviving colour `d` is unrelated to `c i`, so a pairing of a colour with something other than its variance dual, such as a Hermitian pairing of a colour with its conjugate, is covered as much as a metric and its inverse at `τ (c i)`. The two collapses are independent hypotheses: for a propositionally matched pair neither order follows from the other, and there is no cardinality relation between the index sets to supply one. `IsReindexing.unitTensor_pair` is generalised accordingly. The colour list it certifies reads only the surviving colours `a` and `e`, so pinning the second factor's contracted slot to `S.τ d` constrained nothing; freeing it to `b` is what lets both metrics enter `toDualAtIndex` at literal colours, with no transporting `permT` on either factor. That in turn removes the transported second metric and its reconciliation from `toDualMapAtIndex_toDualMapAtIndex`. `toDualAtIndex` is now that equivalence instantiated at `metricTensor (τ (c i))`, `metricTensor (c i)`, and `fromDualMapAtIndex` is the matching `crossToSlotInv`, so the two round trips are the general ones rather than a second hand-rolled inverse. `crossToSlotInv` and the round trip's colour cast are placed at the altitude they belong to: the former in `CrossToSlot`, which is the `CommRing` layer, the latter in `Reindexing` next to `moveLast`, where the two round-trip statements now use it instead of restating it inline. With its API settled, `Dual.lean` takes the module header used across the contraction files — overview, key results, table of contents, references — and lettered body sections, and its declaration docstrings move to the declarative register of those files: each states what the object is, without commentary on the proof mechanics or on the hypotheses of the helper lemmas. Co-authored-by: Claude Opus 4.8 <no-reply+claude-opus-4-8@anthropic.com>
…pdate_dual_dual `crossToSlotEquiv_apply` and `crossToSlotEquiv_symm_apply` were the standard unfolding pair for a `LinearEquiv`, but nothing keys on them: `crossToSlotEquiv` takes eight explicit arguments, so it is not applied by hand, and the downstream `toDualAtIndex_apply` and `toDualAtIndex_symm_apply` are `rfl` rather than rewrites through them. The remaining three declarations of the section are load-bearing — the two round trips are the `left_inv` and `right_inv` arguments of the equivalence and are what `fromDualMapAtIndex_toDualMapAtIndex` and `toDualMapAtIndex_fromDualMapAtIndex` state at map level, before the equivalence exists. `IsReindexing.update_dual_dual` named the identity reindexing between `c` and its twice-dualised colour list. With `IsReindexing.update_update_of_eq` stating that for an arbitrary colour equality, the specialisation carried nothing beyond `by simp [τ_τ_apply]` and had a single use, which now supplies it inline. The section C header prose of `UnitTensorContraction` restated the overview's sentence on the two collapse orders; it now says instead where each of them is used. The proof comments there are trimmed to the transparency behaviour that the tactics do not show. Co-authored-by: Claude Opus 4.8 <no-reply+claude-opus-4-8@anthropic.com>
3bab643 to
fae8463
Compare
|
Rebased onto current master and force-pushed, so the diff reads as new, sorry for that but the commits were becoming complicated. All threads from the last round are addressed and I've replied in each. Thank you both for the time you have put into this. The PR is much larger than the guidelines ask for, and I should have rescoped it before review rather than during. Future work will go up in smaller pieces. What it leaves behind is the raise/lower round trip proved once for any matched pair of rank two tensors that collapses to the unit tensor, at any rank, any slot, and any colours, so the species metric and the Kähler metric are both instances of it. The double dual round trip, which on master held only at rank one and slot zero, now holds at every rank and every slot. |
|
Addressed the latest round; replied in each thread.
|
…name per review Seven of the nine `Fin` lemmas added to `Reindexing.lean` had a single consuming lemma: five fed only `crossToEnd_assoc_rankTwo`, and one each fed `crossToEnd_unitTensor` and `crossToSlot_round_trip_of_unit`. Their proofs now sit inside those lemmas as local hypotheses. `append_castAdd_natAdd_eq_id` and `append_succAbove_const_eq_cycleIcc` remain, each used in three files. Mathlib's `Fin.append_castAdd_natAdd` cannot stand in for the former: its pattern carries the function being split, so it does not match the eta-reduced `Fin.append (Fin.castAdd n) (Fin.natAdd m)`. The local hypotheses are scoped to the branch that uses them and cleared before the closing `grind (splits := 20)`, which ingests the local context. Leaving them in place cost the module ~8.7s to rebuild against ~4.1s. `Fin.reinsert_castAdd` / `reinsert_natAdd` become `succSuccAbove_castAdd_natAdd_apply_castAdd` / `..._apply_natAdd`, naming the embedding each `succSuccAbove` argument enters by, as `succSuccAbove_natAdd_apply_castAdd` does above them. `IsReindexing.moveLast` becomes `move_last`. The `CrossToEnd` overview states the result-to-end and result-to-slot conventions as the colour lists they produce, glosses the output color against `cA` and `cB`, and names the survivors where it first uses the term. Co-authored-by: Claude Opus 4.8 <no-reply+claude-opus-4-8@anthropic.com>
jstoobysmith
left a comment
There was a problem hiding this comment.
Ok, many thanks for iterating - approving this and will merge shortly!
|
I think @nateabr also needs to approve this PR for me to be able to merge it, given there are requested changes above. |
|
Sorry about that :(( |
|
@nateabr Nothing to apologise about :) - tbh, I didn't even know this was a thing until now. |
Slot-addressed cross contraction, and
toDualAtIndexat every rankContracting slot
iof one tensor against slotjof another isprodTthencontrT. Writtenthat way, every call site has to locate the two post-product slots, discharge the distinctness and
τ-duality goals, and repair a cast-polluted output colour before the next step. This PR names thatoperation, fixes its output colour, and proves its algebra once.
Two conventions are provided, and the names say which is which.
crossToEnd i jdeposits the survivor at the end: survivors ofA, then survivors ofB,gaps closed (
Fin.append (cA ∘ i.succAbove) (cB ∘ j.succAbove)). Appending on the right is whatmakes the chain regrouping statable uniformly in rank and slot.
crossToSlot i j Mkeeps the replacement index at sloti(Function.update c i d,nothing else moves) —
T^{μ}{}_{νρ} ↦ T_{μνρ}. This is the front door: contract a distinguishedrank-2 tensor (a metric, the unit tensor) at a named slot of an arbitrary tensor.
crossToSlot_eq_crossToEndis the single place the two meet, the inverse of the cycle[i, last].The payoff is
crossToSlot_raise_lower_round_trip: for any matched pair of rank-2 tensorscollapsing to the unit tensor, raising then lowering a named index is the identity, at any rank and
any slot, with all four colours threaded propositionally so a caller whose metric sits at a literal
colour supplies it with no transporting
permT. On master this result exists only at rank 1, slot 0(
toDualMap_fromDualMap), by 30 lines of hand transports.When the pair collapses in both orders the two contractions are mutually inverse, and
crossToSlotEquivbundles them as a≃ₗbetween the two colour assignments. The species-metriccase is the instance:
toDualAtIndexis reimplemented oncrossToSlot, its double-dual round tripis proved at every rank, and it is upgraded from a
→ₗto a≃ₗ.API changes to existing declarations
Reviewers should decide on these first.
The trade: eleven declarations go (ten in
Dual.lean, one inReindexing.lean), and what replacesthem is a single round trip holding at every rank and every named slot, plus the general
crossToSlotit is an instance of. The deleted
toDualMap_fromDualMapalso carried master'sset_option backward.isDefEq.respectTransparency falseonTensors/Dual.lean:62; the general-rankproof needs no transparency override, so the branch removes that one and adds none.
toDualAtIndexTensors/Dual.lean→ₗat every rank with no lemmas; now a≃ₗ, defined viacrossToSlot. The map itself is split off astoDualMapAtIndex(whatElab.lean'sjigglenow emits).toDualMap,fromDualMap,toDual(+toDualMap_apply,fromDualMap_apply,toDualMap_fromDualMap,fromDualMap_toDualMap,fromDualMap_eq_permT_toDualMap,toDualMap_eq_permT_fromDualMap,toDual_equivariant)Tensors/Dual.leantoDualMapAtIndex/toDualAtIndex, with no in-tree consumers. Note the instance is not literally the old statement: atn = 1, i = 0the colour list isFunction.update ![c] 0 (S.τ c)rather than![S.τ c]. Happy to restore them as thin wrappers if you would rather keep the rank-1 names.IsReindexing.contr_two_rotateTensors/Reindexing.leantoDualAtIndex's output permutation;IsReindexing.crossToSlot_cyclereplaces it.Declarations added
Tensors/Contraction/CrossToEnd.lean(new)crossToEnd— the slot-addressed cross contraction, as a curried bilinear map. Additivity,scalar multiplication and sums in either argument come from the
LinearMapstructure.crossToEnd_two— at rank two on each factor, a plaincontrTof the product on slots1, 2.Discharges the concrete slot arithmetic once so
contrT-level identities lift by one rewrite.crossToEnd_equivariant— commutes with theG-action.crossToEnd_assoc_rankTwo— rebracket the chainA —(iA·0)— B —(last·0)— Cfor rank-twoB,C,up to
permT id. Not full associativity: onlyA's attachment slot is free.crossToEnd_permT_left/crossToEnd_permT_right— move a relabelling of either factor throughthe contraction, the contracted slot moving with it. The survivor relabelling
σ'is a callerargument constrained by the square
(σ i).succAbove ∘ σ' = σ ∘ i.succAbove.Tensors/Contraction/CrossToSlot.lean(new)IsReindexing.crossToSlot_cycle— the output colour list, after the survivor is rotated back.crossToSlot— the result-to-slot contraction against a rank-2 tensor; raising and lowering anamed index.
crossToSlot_eq_crossToEnd— the bridge between the two conventions.crossToSlotInv— the returning contraction, with the round trip's two-Function.updatecolourcast absorbed so a round trip is cast-free at both ends.
crossToSlot_permT_right_id— an identity reindexing of the rank-2 tensor passes through.crossToSlot_equivariant— commutes with theG-action.Tensors/Contraction/UnitTensorContraction.lean(new,[RCLike k])crossToEnd_unitTensor— the unit tensor is an identity forcrossToEndat any named slot.(Private steps:
crossToEnd_prodT_unitTensor,crossToEnd_unitTensor_slot.)IsReindexing.unitTensor_pair— the colour list a matched rank-2 pair contracts to.crossToEnd_round_trip_of_unit_slot— the round trip in result-to-end form.crossToSlot_raise_lower_round_trip— the round trip in result-to-slot form, every colourpropositional. (Private step:
crossToSlot_round_trip_of_unit.)crossToSlotInv_crossToSlot,crossToSlot_crossToSlotInv— both directions, for a paircollapsing in both orders. The second gets its injectivity from the reversed collapse.
crossToSlotEquiv— raising and lowering a named index against a mutually inverse pair, as a≃ₗbetweencandFunction.update c i d.Tensors/Dual.leantoDualMapAtIndex— dualise a named index, ascrossToSlotagainstmetricTensor (τ (c i)).crossToEnd_dual_metricTensor_metricTensor,crossToEnd_metricTensor_metricTensor_eq_dual_unit— the metric collapses against the metric atthe dual colour, in both orders: the two hypotheses
crossToSlotEquivneeds.toDualMapAtIndex_toDualMapAtIndex— the double-dual round trip, at every rank. Its outputreindexing is
IsReindexing.update_update_of_eqatτ (τ (c i)) = c i.fromDualMapAtIndex— the inverse, ascrossToSlotInvagainstmetricTensor (c i).fromDualMapAtIndex_toDualMapAtIndex,toDualMapAtIndex_fromDualMapAtIndex— both directions.toDualMapAtIndex_equivariant—G-equivariance.toDualAtIndex(+_apply,_symm_apply) — the resulting≃ₗ. Not a new name: this redefinesthe existing declaration, see the table above.
Tensors/Reindexing.lean—Finindex maps (newnamespace Finsection)append_swap_val— the.valof the block-swap map, in closed form.append_castAdd_natAdd_eq_id,append_castAdd_cast,append_natAdd_cast— reassembling the twoblocks of a
Finis the identity, or the length cast if one block was cast first.append_succAbove_const_eq_cycleIcc— moving slotito the end is the cycle[i, last].cycleIcc_last_symm_apply_succAbove— the inverse cycle on a reinserted survivor.exists_succAbove_comm— an injection ofFin (n+1)restricts to the survivors of a slot.cast_castAdd_eq_mk,cast_natAdd_eq_mk— pin aFin.cast/block composite to a literal⟨v, _⟩.Tensors/Reindexing.lean—IsReindexingsuccAbove_of_succAbove_eq— the caller-supplied survivor relabelling is itself a reindexing; thesquare
(σ i).succAbove ∘ σ' = σ ∘ i.succAboveforces its injectivity, so no bijectivityhypothesis is needed. Unlike the existing
IsReindexing.succAbove, which builds the relabellingfrom
σas aditecomposite, the map here is the caller's, which is what lets it stand in thestatement of a lemma the caller instantiates.
moveLast— split a colour list at an arbitrary slot rather than at the last one.update_update_of_eq— updating slotitodand back toc ireturnsc: the colour cast around trip at slot
igenerates.Other existing files
Tensors/Contraction/SuccSuccAbove.lean:Fin.reinsert_castAdd,Fin.reinsert_natAdd— reinserta survivor when the two removed slots straddle the two blocks of a product.
Tensors/Contraction/Pure.lean:Pure.permP_dropPair_dropPair_congr,Pure.permP_dropPair_permP_congr— twopermP/dropPairtowers over a common pure tensor agreeonce their composite slot maps agree pointwise.
Pure.contrPCoeff_congr—contrPCoeffisinsensitive to its proof arguments (public, next to
contrPCoeff).Tensors/Contraction/Products.lean:prodT_contrT_fst— the mirror ofprodT_contrT_snd.Tensors/Basic.lean:permT_injective— from the existingpermT_eq_zero_iff.Tensors/Elab.lean:jiggleemitstoDualMapAtIndex; two#guard_msgsupdated accordingly.Imports narrowed to
Dual+Tensorial.Reviewer map
Tensors/Dual.lean— the API change and the removals above. Everything else exists to makethis file possible; if the deletions are unwelcome, say so here first.
Tensors/Reindexing.lean,Contraction/SuccSuccAbove.lean— the index-map lemmas. PureFinarithmetic, no tensor content.Contraction/Pure.lean,Products.lean,Basic.lean— the pure-level congruencesthe long proofs key on.
Contraction/CrossToEnd.lean— the definition and its algebra.crossToEnd_assoc_rankTwoisthe one long proof (66 lines); see the note below.
Contraction/CrossToSlot.lean— small, the convention bridge.Contraction/UnitTensorContraction.lean— the round trips and the equivalence.Tensors/Elab.lean— mechanical rename.Known rough edges
crossToEnd_assoc_rankTwohas a 66-line proof body, over the 50-LOC guideline. The indexarithmetic is factored out into the
Finlemmas above and the closing case analysis isgrind (splits := 20), the idiomSuccSuccAbove.leanalready uses. What remains isproof-specific normalisation: the
contrT_permTpush sequence, thefunPredPredAbovecancellation, and the nine
Fin.cast→literal pinssuccSuccAbove_valneeds to fire. I have notfound a further split that leaves an independently meaningful lemma behind; suggestions welcome.
erwsteps remain, six inUnitTensorContraction.leanand one inDual.lean, eachcommented with the defeq the
rwkeying fails on.set_optionof any kind in the three files this PR adds. On the branch as a whole the netchange is one
backward.isDefEq.respectTransparency falseremoved (it sat on the deletedtoDualMap_fromDualMap) and none added; nomaxHeartbeatsbump anywhere.