wedge() and related functions in the stokes package

Robin K. S. Hankin

wedge
## function (x, ...) 
## {
##     if (nargs() < 3) {
##         wedge2(x, ...)
##     }
##     else {
##         wedge2(x, Recall(...))
##     }
## }
## <bytecode: 0x560d88a78800>
## <environment: namespace:stokes>
wedge2
## function (K1, K2) 
## {
##     if (missing(K2)) {
##         return(K1)
##     }
##     if (is.ktensor(K1) | is.ktensor(K2)) {
##         stop("wedge product only defined for kforms")
##     }
##     if (!is.kform(K1) | !is.kform(K2)) {
##         return(K1 * K2)
##     }
##     if (is.empty(K1) | is.empty(K2)) {
##         return(zeroform(arity(K1) + arity(K2)))
##     }
##     kform(spraycross(K1, K2))
## }
## <bytecode: 0x560d798b9d08>
## <environment: namespace:stokes>

[the meat of wedge2() is kform(spraycross(K1, K2))].

Spivak, in a memorable passage, states:

\(\ldots\) we would like a theorem analogous to 4.1 [the dimensionality of \(k\)-fold tensor products is \(n^k\)]. Of course, if \(\omega\in\Lambda^k(V)\) and \(\eta\in\Lambda^l(V)\), then \(\omega\otimes\eta\) is usually not in \(\Lambda^{k+l}(V)\). We will therefore define a new product, the wedge product \(\omega\wedge\eta\in\Lambda^{k+l}(V)\) by

\[ \omega\wedge\eta=\frac{\left(k+l\right)!}{k!l!}\operatorname{Alt}(\omega\otimes\eta),\qquad\omega\in\Lambda^k(V),\eta\in\Lambda^l(V) \]

(The reason for the strange coefficient will appear later).

- Michael Spivak, 1969 (Calculus on Manifolds, Perseus books). Page 79

Function wedge() returns the wedge product of any number of \(k\)-forms. Function wedge2() returns the wedge product of two \(k\)-forms, although the idiom is somewhat opaque, especially the “strange” combinatorial coefficient \((k+l)!/(k!l!)\).

Digression: function spraycross()

Function wedge() is essentially a convenience wrapper for spraycross(). Function spraycross() it is part of the spray package and gives a tensor product of sparse arrays, interpreted as multivariate polynomials:

(a <- spray(matrix(1:4,2,2),c(2,5)))
##          val
##  2 4  =    5
##  1 3  =    2
(b <- spray(matrix(c(10,11,12,13),2,2),c(7,11)))
##            val
##  11 13  =   11
##  10 12  =    7
spraycross(a,b)
##                val
##  1 3 10 12  =   14
##  1 3 11 13  =   22
##  2 4 10 12  =   35
##  2 4 11 13  =   55
spraycross(b,a)
##                val
##  10 12 1 3  =   14
##  11 13 1 3  =   22
##  10 12 2 4  =   35
##  11 13 2 4  =   55

Observe that spraycross() (and by association wedge()) is associative and distributive but not commutative.

Cut to the chase: wedge2()

Function wedge2() takes two kforms and we will start with a very simple example:

(x <- as.kform(cbind(1,2),5))
## An alternating linear map from V^2 to R with V=R^2:
##          val
##  1 2  =    5
(y <- as.kform(cbind(3,4,7),7))
## An alternating linear map from V^3 to R with V=R^7:
##            val
##  3 4 7  =    7
wedge2(x,y)
## An alternating linear map from V^5 to R with V=R^7:
##                val
##  1 2 3 4 7  =   35

It looks like the combinatorial term has not been included but it has. We will express x and y as tensors (objects of class ktensor) and show how the combinatorial term arises.

 tx <- as.ktensor(x)    # "tx" = tensor 'x'
(ty <- as.ktensor(y))   # "ty" = tensor 'y'
## A linear map from V^3 to R with V=R^7:
##            val
##  7 4 3  =   -7
##  7 3 4  =    7
##  4 7 3  =    7
##  4 3 7  =   -7
##  3 7 4  =   -7
##  3 4 7  =    7

As functions, y and ty are identical:

M <- matrix(round(rnorm(21),2),7,3) # member of (R^7)^3
c(as.function(y)(M),as.function(ty)(M))
## [1] 15.23211 15.23211

Both are equivalent to

7*(
 +M[3,1]*M[4,2]*M[7,3] 
 -M[3,1]*M[4,3]*M[7,2] 
 -M[3,2]*M[4,1]*M[7,3] 
 +M[3,2]*M[4,3]*M[7,1] 
 +M[3,3]*M[4,1]*M[7,2]
 -M[3,3]*M[4,2]*M[7,1]
 )
## [1] 15.23211

We can see that y is a more compact and efficient representation of ty: both are alternating tensors but y has alternatingness built in to its evaluation, while ty is alternating by virtue of including all permutations of its arguments, with the sign of the permutation.

We can evaluate Spivak’s formula (but without the combinatorical term) for \(x\wedge y\) by coercing to ktensors and using tensorprod():

(z <- tensorprod(as.ktensor(x),as.ktensor(y)))
## A linear map from V^5 to R with V=R^7:
##                val
##  1 2 3 4 7  =   35
##  2 1 3 7 4  =   35
##  1 2 4 3 7  =  -35
##  2 1 3 4 7  =  -35
##  1 2 4 7 3  =   35
##  2 1 4 3 7  =   35
##  2 1 4 7 3  =  -35
##  1 2 7 3 4  =   35
##  2 1 7 3 4  =  -35
##  1 2 3 7 4  =  -35
##  1 2 7 4 3  =  -35
##  2 1 7 4 3  =   35

Above, each coefficient is equal to \(\pm 35\) (the sign coming from the sign of the permutation), and we have \(2!3!=12\) rows. We can now calculate \(\operatorname{Alt}(z)\), which would have \(5!=120\) rows, one per permutation of \([5]\), each with coefficient \(\pm\frac{12\times 35}{5!}=\pm 3.5\).

We define \(x\wedge y\) to be \(\frac{5!}{3!2!}\operatorname{Alt}(z)\), so each coefficient would be \(\pm\frac{5!}{3!2!}\cdot\frac{12\times 35}{5!}=35\). We know that \(x\wedge y\) is an alternating form. So to represent it as an object of class kform, we need a kform object with single index entry 1 2 3 4 7. This would need coefficient 35, on the grounds that it is linear, alternating, and maps \(\begin{pmatrix} 1&0&0&0&0\\ 0&1&0&0&0\\ 0&0&1&0&0\\ 0&0&0&1&0\\ 0&0&0&0&0\\ 0&0&0&0&0\\ 0&0&0&0&1 \end{pmatrix}\) to \(35\); and indeed this is what we see:

wedge(x,y)
## An alternating linear map from V^5 to R with V=R^7:
##                val
##  1 2 3 4 7  =   35

So to conclude, the combinatorial term is present in the R idiom, it is just difficult to see at first glance.

Algebraic properties

First of all we should note that \(\Lambda^k(V)\) is a vector space (this is considered in the kform vignette). If \(\omega,\omega_i\in\Lambda^k(V)\) and \(\eta,\eta_i\in\Lambda^l(V)\) then

\[\begin{eqnarray} (\omega_1+\omega_2)\wedge\eta &=& \omega_1\wedge\eta+\omega_2\wedge\eta\\ \omega\wedge(\eta_1+\eta_2) &=&\omega\wedge\eta_1 + \omega\wedge\eta_2\\ \end{eqnarray}\]

(that is, the wedge product is left- and right- distributive); if \(a\in\mathcal{R}\) then

\[\begin{equation} a\omega\wedge\eta = \omega\wedge a\eta=a(\omega\wedge\eta) \end{equation}\]

and \[\begin{equation} \omega\wedge\eta = (-1)^{kl}\eta\wedge\omega\\\ \end{equation}\]

These rules make expansion of wedge products possible by expressing a general kform in terms of basis for \(\Lambda^k(V)\). Spivak tells us that, if \(v_1,\ldots,v_k\) is a basis for \(V\), then the set of all

\[\begin{equation} \phi_{i_1}\wedge\phi_{i_2}\wedge\cdots\wedge\phi_{i_k}\qquad 1\leq i_1 < \cdots < i_n\leq n \end{equation}\]

is a basis for \(\Lambda^k(V)\) where \(\phi_i(v_j)=\delta_{ij}\). The package expresses a \(k\)-form in terms of this basis as in the following example:

(omega <- as.kform(rbind(c(1,2,8),c(1,3,7)),5:6))
## An alternating linear map from V^3 to R with V=R^8:
##            val
##  1 3 7  =    6
##  1 2 8  =    5

In algebraic notation, omega (or \(\omega\)) would be \(5\phi_1\wedge\phi_2\wedge\phi_8+6\phi_1\wedge\phi_3\wedge\phi_7\) and we may write this as \(\omega=5\phi_{128}+6\phi_{137}\). To take a wedge product of this with \(\eta=2\phi_{235}+3\phi_{356}\) we would write

\[\begin{eqnarray} \omega\wedge\eta &=& (5\phi_{128}+6\phi_{137})\wedge (2\phi_{235}+3\phi_{356})\\ &=& 10\phi_{128}\wedge\phi_{235} + 15\phi_{128}\wedge\phi_{356} + 12\phi_{137}\wedge\phi_{235} + 18\phi_{137}\wedge\phi_{356}\\ &=& 10\phi_1\wedge\phi_2\wedge\phi_8\wedge\phi_2\wedge\phi_3\wedge\phi_5 + 15\phi_1\wedge\phi_2\wedge\phi_8\wedge\phi_3\wedge\phi_5\wedge\phi_6\\&{}&\qquad + 12\phi_1\wedge\phi_3\wedge\phi_7\wedge\phi_2\wedge\phi_3\wedge\phi_5 + 18\phi_1\wedge\phi_3\wedge\phi_7\wedge\phi_3\wedge\phi_5\wedge\phi_6\\ &=& 0+ 15\phi_1\wedge\phi_2\wedge\phi_8\wedge\phi_3\wedge\phi_5\wedge\phi_6+0+0\\ &=& -15\phi_1\wedge\phi_2\wedge\phi_3\wedge\phi_5\wedge\phi_6\wedge\phi_8 \end{eqnarray}\]

where we have used the rules repeatedly (especially the fact that \(\omega\wedge\omega=0\) for any alternating form). Package idiom would be:

eta <- as.kform(rbind(c(2,3,5),c(3,5,6)),2:3)
wedge(omega,eta)
## An alternating linear map from V^6 to R with V=R^8:
##                  val
##  1 2 3 5 6 8  =  -15

See how function wedge() does the legwork.