module Algebra.Ring.Module.Multilinear
  {} (R : Ring ) (cr : is-commutative-ring R)
  where

Multilinear maps🔗

We define the module of multilinear maps over a base commutative ring generically over all finite arities. A multilinear map in variables is a function which is separately linear in each variable. We formalise this construction very literally, using finitary dependent products and curried functions.

  field
    map      : Arrᶠ  i   Ms i )  N 

We use the combinators for reasoning about finitary curried functions to express the linearity requirement as follows. For simplicity, assume we are given a tuple and an index The value of does not matter, but having an irredundant representation would be much harder.

If we are given we can consider the updated tuples1 and — which take the same values as on all but the index, where they have the specified value. This corresponds to holding all but the index constant, and allowing only it to vary. Linearity on the value saying that

    linearₚ
      : Πᶠ λ i  (α : Πᶠ λ j   Ms j ) (r :  R ) (x y :  Ms i )
       applyᶠ map (updateₚ α i (r  x + y))
       r  applyᶠ map (updateₚ α i x) + applyᶠ map (updateₚ α i y)
  linear-at
    :  i (xs : Πᶠ λ j   Ms j ) (r :  R ) (x y :  Ms i )
     applyᶠ map (updateₚ xs i (r  x + y))
     r  applyᶠ map (updateₚ xs i x) + applyᶠ map (updateₚ xs i y)
  linear-at = indexₚ linearₚ

  pres-+-at
    :  i (xs : Πᶠ λ j   Ms j ) (x y :  Ms i )
     applyᶠ map (updateₚ xs i (x + y))
     applyᶠ map (updateₚ xs i x) + applyᶠ map (updateₚ xs i y)
  pres-+-at i xs x y =
      ap  e  applyᶠ map (updateₚ xs i (e + y))) (sym (⋆-id _))
    ·· linear-at i xs R.1r x y
    ·· ap₂ _+_ (⋆-id _) refl

  is-group-hom-at
    :  i (xs : Πᶠ λ j   Ms j )
     is-group-hom (Module-on→Group-on (Ms i .snd))
                   (Module-on→Group-on (N .snd))
                   λ m  applyᶠ map (updateₚ xs i m)
  is-group-hom-at i xs .is-group-hom.pres-⋆ x y = pres-+-at i xs x y

  pres-⋆-at
    :  i (xs : Πᶠ λ j   Ms j ) (r :  R ) (x :  Ms i )
     applyᶠ map (updateₚ xs i (r  x))
     r  applyᶠ map (updateₚ xs i x)
  pres-⋆-at i xs r x =
      ap  e  applyᶠ map (updateₚ xs i e)) (sym +-idr)
    ·· linear-at i xs r x 0g
    ·· (ap₂ _+_ refl (is-group-hom.pres-id (is-group-hom-at i xs))  +-idr)

-- XXX: Since we're already squishing down a type whose universe level
-- Agda dislikes, one might wonder if we have to use the finitary
-- products in the type of `pres-+ₚ`. The answer is yes, since
-- otherwise `linearᶠ` lives in Setω, which the declare-record-iso
-- tactic is very unhappy about.

open Multilinear-map
open Multilinear-map using (map) public
open Linear-map using (map)

private unquoteDecl eqv = declare-record-iso eqv (quote Multilinear-map)

private variable
  ℓm ℓn : Level
  M N : Module R ℓm
  n : Nat
  ℓₘ : Fin n  Level
  Ms :  i  Module R (ℓₘ i)

Multilinear-map-path
  :  {n} {ℓₘ : Fin n  Level} {N : Module R ℓn} {Ms : (i : Fin n)  Module R (ℓₘ i)}
    {f g : Multilinear-map n Ms N}
   f .map  g .map
   f  g
Multilinear-map-path {N = N} {f = f} {g = g} p = go where
  module N = Module-on (N .snd)
  go : f  g
  go i .map = p i
  go i .linearₚ = is-prop→pathp
     i  Πᶠ-is-hlevel 1 λ j  Π-is-hlevel³ 1 λ _ _ _  Π-is-hlevel 1 λ _ 
      N .fst .is-tr (applyᶠ (p i) _) (_ N.⋆ applyᶠ (p i) _ N.+ applyᶠ (p i) _))
    (f .linearₚ) (g .linearₚ)
    i

Multilinear-maps
  :  {n} {ℓₘ : Fin n  Level}
      {Ms : (i : Fin n)  Module R (ℓₘ i)}
      {N : Module R ℓn}
   Module-on R (Multilinear-map n Ms N)
Multilinear-maps {n = n} {Ms = Ms} {N = N} = to-module-on mk where
  private
    module Ms i = Module-on (Ms i .snd)
    module N    = Module-on (N .snd)
    instance
      _ = module-notation N
      _ :  {i : Fin n}  Module-notation R  Ms i 
      _ = module-notation (Ms _)

    -- Normally there would be no way in hell these helpers would ever
    -- be useful... except this module needs lossy-unification for
    -- performance reasonsl so we might as well abuse it for style!
    _⟨_⟩
      : Multilinear-map n Ms N
       {_ : Πᶠ  i   Ms i )} {i : Fin n}   Ms i    N 
    _⟨_⟩ f {xs} {i} x = applyᶠ (f .map) (updateₚ xs i x)

    _⟨_⟩ᵤ
      :  {n ℓ'} { : Fin n  Level} {P : (i : Fin n)  Type ( i)} {X : Type ℓ'}
       Arrᶠ P X  {_ : Πᶠ P} {i : Fin n}  P i  X
    _⟨_⟩ᵤ f {xs} {i} x = applyᶠ f (updateₚ xs i x)

    infix 300 _⟨_⟩
    infix 300 _⟨_⟩ᵤ

Note that, since some of the computations involving curried functions of generic length can get pretty gnarly, we separate preservation of addition and of scaling when defining multilinear maps. This makes no semantic difference, but it does simplify the proofs slightly.

We now turn to defining an structure on the set of multilinear maps This is nothing but the pointwise module structure, exactly as in the construction of internal homs in ; However, to keep the interface of Multilinear-map simple to use, we must submit to the conservation of frustration, and power through some nighmarish computations by hand.

  add : Multilinear-map n Ms N  Multilinear-map n Ms N  Multilinear-map n Ms N
  add f g .Multilinear-map.map = zipᶠ _+_ (f .map) (g .map)
  add f g .Multilinear-map.linearₚ = tabulateₚ λ i xs r x y 
    zipᶠ _+_ (f .map) (g .map)  r  x + y ⟩ᵤ         ≡⟨ apply-zipᶠ _ _ _ _ 
    f  r  x + y  + g  r  x + y                  ≡⟨ ap₂ _+_ (linear-at f i xs r x y) (linear-at g i xs r x y) 
    (r  f  x  + f  y ) + (r  g  x  + g  y ) ≡⟨ N.ab.extendr (N.ab.extendl N.+-comm) 
    (r  f  x  + r  g  x ) + (f  y  + g  y ) ≡⟨ ap₂ _+_ (sym (⋆-distribl r _ _)) refl 
    r  (f  x  + g  x ) + (f  y  + g  y )     ≡⟨ ap₂ N._+_ (ap (r N.⋆_) (sym (apply-zipᶠ _ _ _ _))) (sym (apply-zipᶠ _ _ _ _)) 
    r  (zipᶠ _+_ (f .map) (g .map)  x ⟩ᵤ)
      + zipᶠ _+_ (f .map) (g .map)  y ⟩ᵤ             

The example of addition, above, is representative: The flow of these proofs is explicit appeals to the computation rule(s) of curried functions, since the length is generic, surrounding a bit of actual algebra when the underlying operations are exposed enough. The rest of the construction is entirely analogous.

Since it’s so repetitive, it does not need time in the spotlight.
  invm : Multilinear-map n Ms N  Multilinear-map n Ms N
  invm f .map     = mapᶠ N.-_ (f .map)
  invm f .linearₚ = tabulateₚ λ i xs r x y 
    apply-mapᶠ _ _ _ ·· ap N.-_ (linear-at f i xs r x y)
    ·· N.neg-comm
    ·· N.+-comm
    ·· sym (ap₂ N._+_ N.⋆-invr refl)
      sym (ap₂ N._+_ (ap (r N.⋆_) (apply-mapᶠ _ _ _)) (apply-mapᶠ _ _ _))

  scale :  R   Multilinear-map n Ms N  Multilinear-map n Ms N
  scale r f .map = mapᶠ (r N.⋆_) (f .map)
  scale r f .linearₚ = tabulateₚ λ i xs s x y 
    apply-mapᶠ _ _ _
    ·· ap (r N.⋆_) (linear-at f i xs s x y)
    ·· ⋆-distribl _ _ _
      ap₂ N._+_ (N.⋆-assoc _ _ _ ·· ap₂ N._⋆_ cr refl ·· sym (N.⋆-assoc _ _ _)  ap (s N.⋆_) (sym (apply-mapᶠ _ _ _)))
                 (sym (apply-mapᶠ _ _ _))

  open make-module hiding (_+_ ; _⋆_)

  mk : make-module R (Multilinear-map n Ms N)
  mk .has-is-set = Iso→is-hlevel 2 eqv $ Σ-is-hlevel 2 (Arrᶠ-is-hlevel 2 (N .fst .is-tr)) λ x 
    is-prop→is-set $ Πᶠ-is-hlevel 1 λ i 
      Π-is-hlevel³ 1 λ _ _ _  Π-is-hlevel 1 λ _  N .fst .is-tr _ _

  -- Structure
  mk .make-module._+_ = add
  mk .inv = invm
  mk .make-module._⋆_ = scale
  mk .0g .map = constᶠ N.0g
  mk .0g .linearₚ = tabulateₚ λ i xs r x y 
    apply-constᶠ _ (updateₚ xs i (r  x + y))  sym (N.ab.elimr (apply-constᶠ _ _)
     ap (r N.⋆_) (apply-constᶠ _ _)  N.⋆-idr)

  -- Group laws
  mk .+-assoc x y z = Multilinear-map-path $ funextᶠ λ as 
        apply-zipᶠ _ _ _ as
    ·· ap₂ N._+_ refl (apply-zipᶠ _ _ _ _)
    ·· N.+-assoc  ap₂ N._+_ (sym (apply-zipᶠ N._+_ _ _ _)) refl
      sym (apply-zipᶠ N._+_ _ _ _)
  mk .+-invl x = Multilinear-map-path $ funextᶠ λ as 
       apply-zipᶠ _ _ _ _
    ·· ap₂ N._+_ (apply-mapᶠ _ _ _) refl
    ·· N.+-invl
      sym (apply-constᶠ _ _)
  mk .+-idl x = Multilinear-map-path $ funextᶠ λ as 
    apply-zipᶠ _ _ _ _  N.ab.eliml (apply-constᶠ _ _)
  mk .+-comm x y = Multilinear-map-path $ funextᶠ λ as 
    apply-zipᶠ _ _ _ _  N.+-comm  sym (apply-zipᶠ N._+_ _ _ _)

  -- Action laws
  mk .⋆-distribl r x y = Multilinear-map-path $ funextᶠ λ as 
        apply-mapᶠ _ _ _
    ·· ap (r N.⋆_) (apply-zipᶠ _ _ _ _)
    ·· N.⋆-distribl _ _ _
    ·· sym (ap₂ N._+_ (apply-mapᶠ _ _ _) (apply-mapᶠ _ _ _))
    ·· sym (apply-zipᶠ N._+_ _ _ _)
  mk .⋆-distribr r x y = Multilinear-map-path $ funextᶠ λ as 
        apply-mapᶠ _ _ _
    ·· N.⋆-distribr _ _ _
    ·· sym (ap₂ N._+_ (apply-mapᶠ (r N.⋆_) _ _) (apply-mapᶠ (x N.⋆_) _ _))
       sym (apply-zipᶠ N._+_ _ _ _)
  mk .⋆-assoc r s x = Multilinear-map-path $ funextᶠ λ as 
        apply-mapᶠ _ _ _
    ·· ap (r N.⋆_) (apply-mapᶠ _ _ _)
    ·· N.⋆-assoc _ _ _
       sym (apply-mapᶠ _ _ _)
  mk .⋆-id x = Multilinear-map-path $ funextᶠ λ as  apply-mapᶠ _ _ _  N.⋆-id _
Multilinear-Maps
  :  {n} {ℓₘ : Fin n  Level} (Ms : (i : Fin n)  Module R (ℓₘ i)) (N : Module R ℓn)
   Module R (ℓ-maxᶠ ℓₘ  ℓn  )
 Multilinear-Maps Ms N .fst     = Multilinear-map _ Ms N
Multilinear-Maps Ms N .fst .is-tr = Multilinear-maps .Module-on.has-is-set
Multilinear-Maps Ms N .snd = Multilinear-maps

Currying multilinear maps🔗

If we consider the set of maps as an we are forced to consider the linear maps maps For any concrete these have definitionally equal underlying types and module structures, so it stands to reason that we can construct an isomorphism in the generic case, too. Following the standard convention, we refer to this isomorphism as currying, even though, strictly speaking, none of its concrete instances perform any currying.

The construction here is fortunately straightforward, since having successor length is concrete enough for the type of finitary curried functions to compute away. The faff lies in shifting indices in the proofs of linearity.

  curry-multilinear-map
    : Linear-map (Ms fzero) (Multilinear-Maps  i  Ms (fsuc i)) N)
     Multilinear-map (suc n) Ms N
  curry-multilinear-map lin = ml where
    ml : Multilinear-map (suc n) _ _
    ml .map = λ x  lin .map x .map
    ml .linearₚ = tabulateₚ λ where
      fzero xs r x y    
        ap  e  applyᶠ (e .map) (xs .snd)) (Linear-map.linear lin r x y)
        ·· apply-zipᶠ _ _ _ _
        ·· ap₂ N._+_ (apply-mapᶠ _ _ _) refl
      (fsuc i) xs r x y 
        linear-at (lin .map (xs .fst)) i (xs .snd) r x y

  uncurry-multilinear-map
    : Multilinear-map (suc n) Ms N
     Linear-map (Ms fzero) (Multilinear-Maps  i  Ms (fsuc i)) N)
  uncurry-multilinear-map multi = uc where
    uc : Linear-map _ (Multilinear-Maps _ _)
    uc .map x .map     = multi .map x
    uc .map x .linearₚ = tabulateₚ λ i xs 
      Multilinear-map.linear-at multi (fsuc i) (x , xs)

    uc .lin .linear r s t =
      Multilinear-map-path $ funextᶠ λ as 
        linear-at multi fzero (Ms.0g fzero , as) _ _ _
         sym (apply-zipᶠ _ _ _ _  ap₂ N._+_ (apply-mapᶠ _ _ _) refl)

To stress how well these constructions compute, note that, on the components relevant to equality, currying and uncurrying are definitionally isomorphisms.

  uncurry-ml-is-iso : is-iso uncurry-multilinear-map
  uncurry-ml-is-iso = λ where
    .is-iso.inv     curry-multilinear-map
    .is-iso.rinv x  ext λ x  Multilinear-map-path refl
    .is-iso.linv x  Multilinear-map-path $ funextᶠ λ as  refl

  module
    Uncurry = Equiv (_ , is-iso→is-equiv uncurry-ml-is-iso)

  1. Apologies for the notation here — I could not find a way to denote updateₚ α i x that fits well in the mathematical prose.↩︎