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 :)
| module | ||
|
|
||
| public import Physlib.Relativity.Tensors.Constructors | ||
| public import Physlib.Relativity.Tensors.Contraction.Bond |
There was a problem hiding this comment.
I'm a bit concerned about this import being here, is it a good design choice to import high level algebra into a a basic module like this? I think down the line this might cause some issues with compilation as this file is imported in many other files.
Furthermore I do believe the lemmas you have placed in this file ideally should've been in bond.lean.
| · simp [Fin.append_left] | ||
| · simp [Fin.append_right] | ||
|
|
||
| end ReindexMapVal |
There was a problem hiding this comment.
Do the lemmas above need to be private? They seem quite helpful maybe you should move them to reindexing.lean so they can be used later.
| congr 3 | ||
| apply Fin.ext | ||
| simp | ||
|
|
There was a problem hiding this comment.
this could also be made public and moved to reindexing.lean?
| split_ifs <;> omega | ||
| · intro r t ht; simp only [map_smul, ht] | ||
| · intro t1 t2 h1 h2; simp only [map_add, h1, h2] | ||
|
|
There was a problem hiding this comment.
For the proofs above I think its worth heavily refactoring them(especially since you needed to increase the heartbeats). Particularly the generalize blocks.
Maybe try isolating the index logic into standalone lemmas (consider adding them to reindexing.lean).
| (prodT E (contrT 1 0 1 hij (prodT B (unitTensor cB)))) := | ||
| contrT_prodT_snd (0 : Fin 3) 1 hij (prodT B (unitTensor cB)) E | ||
| rw [contrT_single_unitTensor] at key | ||
| conv_lhs => enter [2, 2]; rw (transparency := .instances) [key] |
There was a problem hiding this comment.
Overriding the transperency like this can be very brittle especially during mathlib bumps.
Could we try replacing the raw rw with a congruence-based approach instead? Using something like refine or explicit congruence lemmas (which are available in reindexing.lean I think) usually unifies these dependent types naturally without needing to override transparency or target specific coordinates.
|
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. |
…general-rank double-dual round trip; retire contr_two_rotate
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 |
idReindex is an intentional Prop-valued reducible def: the permT/crossContrT rewrite lemmas need its body to unfold so the underlying id permutation is matchable, which a theorem would seal off. Suppress the defLemma linter instead of converting it.
|
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:
|
Rename the two slot-contraction operations so the names carry the result-placement convention: `crossContrT` -> `crossToEnd` (survivors of both factors appended at the end) and `contrAtSlot` -> `crossToSlot` (replacement index kept at the slot). Add the connecting lemma `crossToSlot_eq_crossToEnd`, name the collapse theorems `crossToEnd_round_trip_of_unit` and `crossToSlot_raise_lower_roundtrip` (folding the earlier round-trip alias into the latter), and lead the module docstrings with the result-to-end vs result-to-slot split. No behaviour change beyond the renamed identifiers.
… target colour Generalize `crossToSlot_raise_lower_roundtrip` so the inverse metric and the surviving index land at an arbitrary target colour `e` propositionally equal to `c i`, with the collapse hypothesis stated at `unitTensor e`. A consumer whose inverse metric sits at a literal colour then supplies it with no `permT` transport and recovers the index at that literal. The same-colour species-metric case is the `e := c i` instance, so `toDualAtIndex_toDualAtIndex` consumes the one lemma with `he := rfl`; the specialized variant is retired and the canonical name kept.
cae0313 to
3bab643
Compare
Slot-addressed cross contraction and the general-rank raise/lower round trip
What the SUSY layer needs
The downstream SUSY layer (an N=1 Kähler stack) raises and lowers a named tensor index with the
Kähler metric
g_{IJ̄}, and proves raising and lowering mutually inverse:raise (lower V) = V, theoperator form of
g_{IJ̄} g^{J̄K} = δ_I^Kon the physical domain. The existing tensor API does notprovide three things this requires.
g_{IJ̄}carries different colours on its two slots(chiral
I, antichiralJ̄), so lowering achiralUpindex yields anantiDownone. The existingdual
toDualAtIndexcontracts against the species metric, whose slots share a colour and whosesurvivor is pinned to
S.τ (c i). It is the wrong shape.gand its inverseg⁻¹aremodel-specific rank-2 tensors, and
g · g⁻¹ = δcontracts one against the other.toDualAtIndexcontracts a single tensor against the fixed species metric, so it cannot express a contraction of two
such tensors. Upstream's rank-1
toDualMap_fromDualMapcollapses the species metric with itself, nota metric against its inverse.
raise ∘ lower = idchains(V·g)·g⁻¹and regroups toV·(g·g⁻¹)so thepair collapses. Under raw
contrT (prodT …)the intermediate lands at a cast-polluted colour and thetwo bracketings sit at different types, so the reassociation is not a direct same-typed equality
without explicit transports.
Defining a different-colour dual is cheap, barely more than
toDualAtIndexwith a metric parameter.The work is its law, that raising and lowering are mutually inverse for a mixed-colour metric.
That law is a two-tensor contraction plus a chain reassociation. This machinery is the algebra of the
inverse law, not the definition of the operation.
The two conventions
Every slot contraction has to decide where the surviving index lands. The PR carries both conventions,
and the names say which is which.
crossToEnd i jcontracts slotiof one tensor against slotjof another and deposits theresult at the end: survivors of
A, then survivors ofB, gaps closed(
Fin.append (cA ∘ i.succAbove) (cB ∘ j.succAbove) : Fin (nA+nB) → C). Appending survivors on theright is associative, which is what makes
crossToEnd_chain_assocreduce to aFin.castuniformlyin rank and slot.
crossToSlot i Mperforms the same contraction but keeps the replacement index at sloti(
Function.update c i d, nothing else moved), the convention raising/lowering andtoDualAtIndexneed (
T^μ{}_νρ ↦ T_μνρ). It is the front door: contract a distinguished rank-2 tensor (a metric,the unit tensor) at a named slot of an arbitrary tensor.
crossToSlot_eq_crossToEndis the single place the two conventions meet, the cycle(Fin.cycleIcc i (Fin.last nA)).symmthat walks the result from the end back to sloti. Thedocstrings lead with this result-to-end vs result-to-slot split:
crossToSlotis the API,crossToEndthe substrate it is built on.The machinery
crossToEnd i jcontracts slotiof one tensor against slotjof another. It adds no expressivepower over
permT (contrT (prodT …)). It names the operation and normalises its output colour, so itsalgebra (left-linearity, equivariance, chain associativity, the relabelling commutators) is proved once
against clean slot variables. This does not reduce associativity to count arithmetic: the central proof
still compares contraction coefficients, survivor maps, colour preservation, and count reindexings.
The gain is that these obligations are discharged once at a canonical public normal form rather than
rebuilt from raw casts by each consumer.
crossToSlot i Mraises or lowers a named index by contracting against a rank-2 tensorM : ![S.τ (c i), d], with the survivor colourdfree. The SUSY layer consumes this directly:KahlerMetric.lowerandraisearecrossToSlot i (g u)andcrossToSlot i (gInv u)at a named slot.crossToEnd_chain_associs the reassociation that makes the round trip provable. Built on it,crossToEnd_round_trip_of_unitis the matched-pair collapse, andcrossToSlot_raise_lower_roundtripis the round-trip theorem
KahlerMetric.raise_lowerconsumes, with the metric'srightInversefield(
g · g⁻¹ = unit) as its hypothesis.The round trip, parameterized by where the raised index lands
crossToSlot_raise_lower_roundtripis the general-rank raise/lower round trip: contracting slotiagainst a rank-2 tensor
M, then against its inverseM', returns the tensor, at any rank and anyslot. It is parameterized by a target survivor colour
e, propositionally equal toc i:M'and the surviving index land ate, not forced toc i. A consumer whose inverse metric alreadysits at a fixed colour then supplies it directly, with no
permTtransport, and recovers the index atthat colour. The same-colour case
e := c i— species metric, survivor pinned toS.τ (c i)— is thehe := rflinstance; there is one lemma, not a general one plus a specialized one.The
toDualAtIndexresultThe species-metric case, both slots one colour and survivor pinned to
S.τ (c i), is the degenerateinstance of the same machinery. On it,
toDualAtIndexiscrossToSlot i (metricTensor (S.τ (c i))),and the double-dual round trip
toDualAtIndex_toDualAtIndex(dualising a named index twice returns theoriginal, at general rank and arbitrary slot) is the
e := c i(he := rfl) consumer ofcrossToSlot_raise_lower_roundtrip. The index-notation elaborator (Tensors/Elab.lean) consumestoDualAtIndex;toDualAtIndex_toDualAtIndexis a reusable result with no other in-tree consumer.Relation to
prodT+contrTcrossToEndis definitionallypermT … (contrT … (prodT t1 t2)). Two properties justify wrapping it.contrTof aprodTlands at colourFin.append cA cB ∘ (succSuccAbove/cast composite), not a clean function of the survivors, so a second contraction needs hand-insertedrecasts.
crossToEndnormalises the output toFin.append (cA ∘ i.succAbove) (cB ∘ j.succAbove) : Fin (nA+nB) → C. Without itcrossToEnd_chain_associs not statable as a direct same-typed equality without explicittransports; its two sides sit at different cast-laden colours.
orders, or moving a relabelling through a contraction, would be re-derived per site against that
site's indices and casts. Naming the operation proves them once.
crossToSlot_round_trip_of_unitis the forcing example. Raising a named slot withM₁and lowering itback with
M₂returns the original whenevercrossToEnd M₁ M₂is the unit tensor. The two slotcolours are independent, so this is the conjugate-metric case, and it reduces to
crossToEnd_chain_assocat(nB, nC) = (0, 1), with the ranknAgeneral.Consumers
The rest of the API is justified by the SUSY layer.
KahlerMetric.lowerandraisearecrossToSlot i (g u)andcrossToSlot i (gInv u)at a namedslot of an arbitrary-rank tensor.
rightInversefield statesg · g⁻¹ = unitas acrossToEndequation, in the metric'sspecification.
raise_loweris a direct instance ofcrossToSlot_raise_lower_roundtrip. Becauseg_{IJ̄}andg^{J̄I}carry literal colours, raising the lowered index lands the survivor at the literalchiralUp, not atc i— the target-colour case (e = chiralUp) the round trip is parameterizedfor. The inverse metric
g^{J̄I}enters at its native colour with nopermTtransport, and thesurvivor lands where
raise's output type expects it. Threadingeinto the shared lemma is whatabsorbs the identity recasts that
lower/raiseintroduce, which the intrinsic-colour layer doesnot normalise cheaply; without it the round trip had to be assembled at the
crossToEndlevel. Soraise_loweris now a one-callcrossToSlot-level instance, proved once at general rank.toDualAtIndex_toDualAtIndexis the same lemma at the same-colour instancee := c i. Both consumersdraw on one
crossToSlot_raise_lower_roundtrip.Costs
Tensor S cis indexed byc. Type identity is definitional and colour equalities are propositional,so transport across equal colours is
Eq.rec/cast.LinearEquiv.cast, so components comeout as stacked casts.
pure_double_cast_congrproves that two double-cast component expressionsagree once their source indices match (the cast proofs are then proof-irrelevant), and the two towers
from the left- and right-associated orders agree once their composite leg-indices match
(
permP_dropPair_permP_congr,permP_dropPair_dropPair_permP_congr).set_option backward.isDefEq.respectTransparency falseand nomaxHeartbeatsbump;
crossToEnd_chain_assocbuilds at default heartbeats. The pre-existingProducts.leanspectator lemmas the chain reuses (e.g.
prodT_contrT_snd) carry upstream'srespectTransparency false, where a slot composite is defeq only underdefaulttransparency.Scope
crossToEnd_chain_associs associativity of the canonical three-tensor chain. Withμthe bondbetween
AandBandνthe bond betweenBandC, each a summedτ-dual pair:A's attachment slotiAcarriesμand may sit anywhere inA's index list.B's slot0carriesμand itslastslot carriesν;C's slot0carriesν. SoBis contracted at bothendpoints, and only
iAis free. Associativity(A·B)·C = A·(B·C)says that summingμfirst orνfirst gives the same tensor. In index notation the two Einstein sums are independent and the expression
carries no brackets. The mathematical content is that the two independent contractions commute (the
two scalar contraction coefficients occur in opposite orders); most of the formal proof is the
positional, colour, and dependent-type bookkeeping that makes Lean see the two orders at compatible
types. It is not full associativity for arbitrary slots; a non-canonical slot on
BorCis reachedby conjugating with the
permTcommutators. This is the shape the Kähler round trip andtoDualAtIndex_toDualAtIndexconsume. The unit-tensor material isRCLike-valued and lives in its ownmodule, apart from the
CommRingalgebra.Review order
Recommended reading, following the dependency order of the commits:
Reindexing.lean: theFinblock-map and induced-permutation lemmas the contraction rests on.Contraction/Basic.leanandContraction/Products.lean:contrT_castand the spectatorcommutations used by the chain lemmas.
Contraction/CrossContraction.lean:crossToEnd,crossToSlot,crossToSlot_eq_crossToEnd, andthe algebra (
crossToEnd_chain_associs the centre).Contraction/UnitTensorContraction.lean: the unit-collapse identities,crossToEnd_round_trip_of_unit, andcrossToSlot_raise_lower_roundtrip.Contraction/MetricDual.lean:toDualAtIndexand its double-dual round trip, the same-colourinstance of the round-trip lemma.
Files changed
Legend: bold = public API; plain = private proof helper.
Physlib/Relativity/Tensors/Contraction/CrossContraction.lean(new)The primitive, its linear and associative algebra, contraction at a named slot, and the bridge between
the two survivor conventions.
crossToEnd: the slot-addressed cross contraction, result at the end (def).crossToEnd_two: at rank two on each factor, the(last, 0)contraction is a plaincontrTof the product on the canonical middle slots. It discharges the slot arithmetic once, so
contrT-level identities lift tocrossToEndby one rewrite.crossToEnd_add_left/_smul_left/_sum_left: additivity, scalar-multiplication, andfinite-sum distributivity in the left factor.
crossToEnd_equivariant: commutes with theG-action.crossToEnd_chain_assoc: associativity of the canonical chainA^{…μ…} B_{μ}{}^{…}{}_{ν} C^{ν}{}_{…}((A·B)·C = A·(B·C)), withA's attachment slot free andB/Cpinned to theirendpoints, with explicit count reindexings for the regrouped final survivor count and the
intermediate
B·Ctensor.crossToEnd_permT_left: move an arbitrary relabelling of the left factor through thecontraction, sending the contracted slot to
σ iand relabelling survivors by the inducedpermutation.
crossToEnd_permT_left_of_fix/_permT_right_of_fix: the same for a relabelling that fixesthe contracted slot.
IsReindexing.crossToSlot_cycle: the survivor relabelling (inverse cycle[i, last]) forcrossToSlot.crossToSlot: contract slotiagainst slot0of a rank-2M : ![S.τ (c i), d]and rotatethe survivor back to
i(def).crossToSlot_eq_crossToEnd: the bridge,crossToSlot i M tas a cycle ofcrossToEnd i 0 t M.crossToSlot_ofColourEq:crossToSlotabsorbing a propositional first-colour equality, soMmay enter at a colour equal only up to an involution (def).
crossToSlot_ofColourEq_applyis itsapplied form.
crossToEnd_reinsert_castAdd/_reinsert_natAdd(survivor reinsertion afterdropping a slot from each factor);
contrPCoeff_congr(the contraction coefficient is insensitive toits proof arguments once the two contracted slots agree);
crossToEnd_chain_assoc_survivor_arith(the arithmetic core of the survivor-slot comparison in
crossToEnd_chain_assoc); the cast-towercongruence
pure_double_cast_congrand the survivor-tower closerspermP_dropPair_permP_congr/permP_dropPair_dropPair_permP_congr.Physlib/Relativity/Tensors/Contraction/UnitTensorContraction.lean(new)The unit tensor is the identity for slot contraction; the raise/lower round trips. (
[RCLike k].)IsReindexing.moveLast: the reindexing carrying slotito the survivor tail.crossToEnd_last_unitTensor: contracting the last slot againstunitTensorreturns thetensor, slot carried to the end (boundary case).
crossToEnd_unitTensor: the unit tensor is an identity forcrossToEndat any slot.crossToEnd_round_trip_of_unit: two contractions against a pair that collapses to the unittensor return the original tensor, up to the cycle moving the round-tripped slot back.
crossToSlot_permT_right_id: an identity recast of the rank-2 tensor in acrossToSlotbecomesthe corresponding identity recast of the output.
crossToSlot_round_trip_of_unit: the round trip incrossToSlotform (rawpermT-caststatement).
crossToSlot_raise_lower_roundtrip: the same incrossToSlot_ofColourEqform,M'entering atits natural colour and the survivor landing at a target colour
e = c i; the shapeKahlerMetric.raise_lowerandMetricDual.toDualAtIndex_toDualAtIndexconsume.pure_cast_congr(Pure.congr_midwrapper);crossToEnd_prodT_unitTensor(rank-one spectator seed);
crossToEnd_unitTensor_slot/crossToEnd_round_trip_of_unit_slot(restatements at a propositionally-last slot);
succAboveInduced_cycleIcc_symm(the inverse cycleinduces the identity survivor permutation).
Physlib/Relativity/Tensors/Contraction/MetricDual.lean(new)Named-index dual via metric contraction, on
crossToSlot. ImportsUnitTensorContractionandMetricTensor.toDualAtIndex: dualise a named index against the species metric,crossToSlot i (metricTensor (S.τ (c i)))(the same-colour, survivor-pinned case). Consumed by theindex-notation elaborator.
toDualAtIndex_toDualAtIndex: dualising a named index twice returns the original tensor, up tothe colour cast from the two
Function.updates. Thee := c iinstance ofcrossToSlot_raise_lower_roundtrip; general rank, arbitrary slot.Physlib/Relativity/Tensors/Reindexing.leanNew
Finblock-map and induced-permutation lemmas, plus anid-witness helper.IsReindexingitselfis unchanged (still
def := Function.Bijective σ ∧ ∀ i, c (σ i) = c1 i).idReindex: reducibleidreindexing witness from a pointwise colour equality. Its reducibilitykeeps the resulting proof matchable under
instancestransparency by thepermT/crossToEndrewrite lemmas.
Fin.append_swap_val/append_castAdd_cast_natAdd_val/append_castAdd_natAdd_cast_val: the.valof the product and contraction block maps as explicit piecewise formulas, soomegaclosesslot arithmetic without exponential
split_ifs.Fin.append_castAdd_natAdd_eq_id: the natural-order block map is the identity (idspecialisation of Mathlib's
Fin.append_castAdd_natAdd).Fin.append_succAbove_const_eq_cycleIcc: listing thei.succAbovesurvivors theniis thecycle
[i, last].Fin.succAboveInduced/succAbove_succAboveInduced/succAboveInduced_bijective/succAboveInduced_id: the map on thei.succAbovesurvivors of slotiinduced by a bijectionσ, landing in the complement ofσ i.succAbove_succAboveInduced_of_fixgives its definingproperty when
σfixesi.Fin.eq_of_bijective: a bijectionFin m → Fin nforcesm = n.Physlib/Relativity/Tensors/Contraction/Products.leanprodT_contrT_fst: a contraction internal to the left factor commutes past an outer product witha right spectator (mirror of
prodT_contrT_sndviaprodT_swap).contrT_prodT_fst: the same read in reverse.Physlib/Relativity/Tensors/Contraction/Basic.leancontrT_cast: commute aFin.castreshape of the rank throughcontrT.Physlib/Relativity/Tensors/Dual.leanandPhyslib/Relativity/Tensors/Elab.leantoDualAtIndexlives inMetricDual.lean;Dual.leancarries only the rank-1toDual/toDualMap/fromDualMapfamily (unchanged from upstream).Elab.lean's import is repointedfrom
DualtoMetricDual(its only use wastoDualAtIndex).Physlib.leanImports of the new
CrossContraction,UnitTensorContraction, andMetricDualmodules.