contract
function (K, v, lose = TRUE) 
{
    if (is.vector(v)) {
        out <- Reduce("+", Map("*", apply(index(K), 1, contract_elementary, 
            v), elements(coeffs(K))))
    }
    else {
        stopifnot(is.matrix(v))
        out <- K
        for (i in seq_len(ncol(v))) {
            out <- contract(out, v[, i, drop = TRUE], lose = FALSE)
        }
    }
    if (lose) {
        out <- lose(out)
    }
    return(disordR::drop(out))
}
contract_elementary
function (o, v) 
{
    out <- zeroform(length(o) - 1)
    for (i in seq_along(o)) {
        out <- out + (-1)^(i + 1) * v[o[i]] * as.kform(rbind(o[-i]), 
            lose = FALSE)
    }
    return(out)
}

To cite the stokes package in publications, please use Hankin (2022); this function monograph discusses function contract(). Given a kk-form ฯ•:Vkโ†’โ„\phi\colon V^k\longrightarrow\mathbb{R} and a vector ๐ฏโˆˆV\mathbf{v}\in V, the contraction ฯ•๐ฏ\phi_\mathbf{v} of ฯ•\phi and ๐ฏ\mathbf{v} is a kโˆ’1k-1-form with

ฯ•๐ฏ(๐ฏ1,โ€ฆ,๐ฏkโˆ’1)=ฯ•(๐ฏ,๐ฏ1,โ€ฆ,๐ฏkโˆ’1) \phi_\mathbf{v}\left(\mathbf{v}^1,\ldots,\mathbf{v}^{k-1}\right) = \phi\left(\mathbf{v},\mathbf{v}^1,\ldots,\mathbf{v}^{k-1}\right)

provided k>1k>1; if k=1k=1 we specify ฯ•๐ฏ=ฯ•(๐ฏ)\phi_\mathbf{v}=\phi(\mathbf{v}). If Spivak (1965) does discuss this, I have forgotten it. Function contract_elementary() is a low-level helper function that translates elementary kk-forms with coefficient 1 (in the form of an integer vector corresponding to one row of an index matrix) into its contraction with ๐ฏ\mathbf{v}; function contract() is the user-friendly front end. We will start with some simple examples. I will use phi and ฯ•\phi to represent the same object.

(phi <- as.kform(1:5))
## An alternating linear map from V^5 to R with V=R^5:
##                val
##  1 2 3 4 5  =    1

Thus k=5k=5 and we have ฯ•=dx1โˆงdx2โˆงdx3โˆงdx4โˆงdx5\phi=\mathrm{d}x^1\wedge\mathrm{d}x^2\wedge \mathrm{d}x^3\wedge\mathrm{d}x^4\wedge\mathrm{d}x^5. We have that ฯ•\phi is a linear alternating map with

ฯ•([10000],[01000],[00100],[00010],[00001])=1\phi\left(\begin{bmatrix}1\\0\\0\\0\\0\end{bmatrix}, \begin{bmatrix}0\\1\\0\\0\\0\end{bmatrix}, \begin{bmatrix}0\\0\\1\\0\\0\end{bmatrix}, \begin{bmatrix}0\\0\\0\\1\\0\end{bmatrix}, \begin{bmatrix}0\\0\\0\\0\\1\end{bmatrix} \right)=1.

The contraction of ฯ•\phi with any vector ๐ฏ\mathbf{v} is thus a 44-form mapping V4V^4 to the reals with ฯ•๐ฏ(๐ฏ1,๐ฏ2,๐ฏ3,๐ฏ4)=ฯ•(๐ฏ,๐ฏ1,๐ฏ2,๐ฏ3,๐ฏ4)\phi_\mathbf{v}\left(\mathbf{v}^1,\mathbf{v}^2,\mathbf{v}^3,\mathbf{v}^4\right)=\phi\left(\mathbf{v},\mathbf{v}^1,\mathbf{v}^2,\mathbf{v}^3,\mathbf{v}^4\right). Taking the simplest case first, if ๐ฏ=(1,0,0,0,0)\mathbf{v}=(1,0,0,0,0) then

v <- c(1,0,0,0,0)
contract(phi,v)
## An alternating linear map from V^4 to R with V=R^5:
##              val
##  2 3 4 5  =    1

that is, a linear alternating map from V4V^4 to the reals with

ฯ•๐ฏ([01000],[00100],[00010],[00001])=1\phi_\mathbf{v}\left( \begin{bmatrix}0\\1\\0\\0\\0\end{bmatrix}, \begin{bmatrix}0\\0\\1\\0\\0\end{bmatrix}, \begin{bmatrix}0\\0\\0\\1\\0\end{bmatrix}, \begin{bmatrix}0\\0\\0\\0\\1\end{bmatrix}\right)=1.

(the contraction has the first argument of ฯ•\phi understood to be ๐ฏ=(1,0,0,0,0)\mathbf{v}=(1,0,0,0,0)). Now consider ๐ฐ=(0,1,0,0,0)\mathbf{w}=(0,1,0,0,0):

w <- c(0,1,0,0,0)
contract(phi,w)
## An alternating linear map from V^4 to R with V=R^5:
##              val
##  1 3 4 5  =   -1

ฯ•๐ฐ([00100],[10000],[00010],[00001])=1orฯ•๐ฐ([10000],[00100],[00010],[00001])=โˆ’1\phi_\mathbf{w}\left( \begin{bmatrix}0\\0\\1\\0\\0\end{bmatrix}, \begin{bmatrix}1\\0\\0\\0\\0\end{bmatrix}, \begin{bmatrix}0\\0\\0\\1\\0\end{bmatrix}, \begin{bmatrix}0\\0\\0\\0\\1\end{bmatrix}\right)=1 \qquad\mbox{or}\qquad \phi_\mathbf{w}\left( \begin{bmatrix}1\\0\\0\\0\\0\end{bmatrix}, \begin{bmatrix}0\\0\\1\\0\\0\end{bmatrix}, \begin{bmatrix}0\\0\\0\\1\\0\end{bmatrix}, \begin{bmatrix}0\\0\\0\\0\\1\end{bmatrix}\right)=-1.

Contraction is linear, so we may use more complicated vectors:

contract(phi,c(1,3,0,0,0))
## An alternating linear map from V^4 to R with V=R^5:
##              val
##  2 3 4 5  =    1
##  1 3 4 5  =   -3
contract(phi,1:5)
## An alternating linear map from V^4 to R with V=R^5:
##              val
##  1 2 3 4  =    5
##  1 2 4 5  =    3
##  2 3 4 5  =    1
##  1 3 4 5  =   -2
##  1 2 3 5  =   -4

We can check numerically that the contraction is linear in its vector argument: ฯ•a๐ฏ+b๐ฐ=aฯ•๐ฏ+bฯ•๐ฐ\phi_{a\mathbf{v}+b\mathbf{w}}= a\phi_\mathbf{v}+b\phi_\mathbf{w}.

a <- 1.23
b <- -0.435
v <- 1:5
w <- c(-3, 2.2, 1.1, 2.1, 1.8)

contract(phi,a*v + b*w) == a*contract(phi,v) + b*contract(phi,w)
## [1] TRUE

We also have linearity in the alternating form: (aฯ•+bฯˆ)๐ฏ=aฯ•๐ฏ+bฯˆ๐ฏ(a\phi+b\psi)_\mathbf{v}=a\phi_\mathbf{v} + b\psi_\mathbf{v}.

(phi <- rform(2,5))
## An alternating linear map from V^5 to R with V=R^7:
##                val
##  2 3 4 5 7  =   -2
##  1 3 4 6 7  =    1
(psi <- rform(2,5))
## An alternating linear map from V^5 to R with V=R^7:
##                val
##  1 2 3 6 7  =    2
##  2 3 5 6 7  =    1
a <- 7
b <- 13
v <- 1:7
contract(a*phi + b*psi,v) == a*contract(phi,v) + b*contract(psi,v)
## [1] TRUE

Contraction of products

Weintraub (2014) gives us the following theorem, for any kk-form ฯ•\phi and ll-form ฯˆ\psi:

(ฯ•โˆงฯˆ)๐ฏ=ฯ•๐ฏฯˆ+(โˆ’1)kฯ•โˆงฯˆ๐ฏ. \left(\phi\wedge\psi\right)_\mathbf{v} = \phi_\mathbf{v}\psi + (-1)^k\phi\wedge\psi_\mathbf{v}.

We can verify this numerically with k=4,l=5k=4,l=5:

phi <- rform(terms=5,k=3,n=9)
psi <- rform(terms=9,k=4,n=9)
v <- sample(1:100,9)
contract(phi^psi,v) ==  contract(phi,v) ^ psi - phi ^ contract(psi,v)
## [1] TRUE

The theorem is verified. We note in passing that the object itself is quite complicated:

summary(contract(phi^psi,v))
## A kform object with 47 terms.  Summary of coefficients: 
## 
## a disord object with hash d6cdb7213e60a9d847f1752a839f18b1de98bc57 
## 
##     Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
## -2943.00  -516.00    48.00    44.47   768.00  2625.00 
## 
## 
## Representative selection of index and coefficients:
## 
## An alternating linear map from V^6 to R with V=R^9:
##                    val
##  1 2 4 6 8 9  =    390
##  1 2 3 5 6 7  =    420
##  1 2 3 4 6 8  =   -840
##  2 5 6 7 8 9  =   1605
##  1 2 3 6 7 9  =    355
##  1 2 3 6 8 9  =  -1200

We may also switch ฯ•\phi and ฯˆ\psi, remembering to change the sign:

contract(psi^phi,v) ==  contract(psi,v) ^ phi + psi ^ contract(phi,v)
## [1] TRUE

Repeated contraction

It is of course possible to contract a contraction. If ฯ•\phi is a kk-form, then (ฯ•๐ฏ)๐ฐ\left(\phi_\mathbf{v}\right)_\mathbf{w} is a kโˆ’2k-2 form with

(ฯ•๐ฎ)๐ฏ(๐ฐ1,โ€ฆ,๐ฐkโˆ’2)=ฯ•(๐ฎ,๐ฏ,๐ฐ1,โ€ฆ,๐ฐkโˆ’2) \left(\phi_\mathbf{u}\right)_\mathbf{v}\left(\mathbf{w}^1,\ldots,\mathbf{w}^{k-2}\right)=\phi\left(\mathbf{u},\mathbf{v},\mathbf{w}^1,\ldots,\mathbf{w}^{k-2}\right)

And this is straightforward to realise in the package:

(phi <- rform(2,5))
## An alternating linear map from V^5 to R with V=R^7:
##                val
##  1 4 5 6 7  =   -2
##  2 4 5 6 7  =   -1
u <- c(1,3,2,4,5,4,6)
v <- c(8,6,5,3,4,3,2)
contract(contract(phi,u),v)
## An alternating linear map from V^3 to R with V=R^7:
##             val
##  2 5 6  =    10
##  1 4 7  =     2
##  4 5 7  =    73
##  1 4 5  =    20
##  2 4 7  =     1
##  1 5 6  =    20
##  2 4 6  =   -14
##  1 6 7  =    -2
##  2 6 7  =    -1
##  2 4 5  =    10
##  5 6 7  =    73
##  4 6 7  =   -90
##  1 4 6  =   -28
##  4 5 6  =  -122

But contract() allows us to perform both contractions in one operation: if we pass a matrix MM to contract() then this is interpreted as repeated contraction with the columns of MM:

M <- cbind(u,v)
contract(contract(phi,u),v) == contract(phi,M)
## [1] TRUE

We can verify directly that the system works as intended. The lines below strip successively more columns from argument V and contract with them:

(o <- kform(spray(t(replicate(2, sample(9,4))), runif(2))))
## An alternating linear map from V^4 to R with V=R^9:
##                     val
##  3 7 8 9  =  -0.1482116
##  1 5 6 7  =   0.4314737
V <- matrix(rnorm(36),ncol=4)
jj <- c(
   as.function(o)(V),
   as.function(contract(o,V[,1,drop=TRUE]))(V[,-1]), # scalar
   as.function(contract(o,V[,1:2]))(V[,-(1:2),drop=FALSE]),
   as.function(contract(o,V[,1:3]))(V[,-(1:3),drop=FALSE]),
   as.function(contract(o,V[,1:4],lose=FALSE))(V[,-(1:4),drop=FALSE])
)
print(jj)
## [1] -0.4992204 -0.4992204 -0.4992204 -0.4992204 -0.4992204
max(jj) - min(jj) # zero to numerical precision
## [1] 2.775558e-16

and above we see agreement to within numerical precision. If we pass three columns to contract() the result is a 00-form:

## [1] -0.4992204

In the above, the result is coerced to a scalar which is returned in the form of a disord object; in order to work with a formal 00-form (which is represented in the package as a spray with a zero-column index matrix) we can use the lost=FALSE argument:

contract(o,V,lose=FALSE)
## An alternating linear map from V^0 to R with V=R^0:
##             val
##   =  -0.4992204

thus returning a 00-form. If we iteratively contract a kk-dimensional kk-form, we return the determinant, and this may be verified as follows:

o <- as.kform(1:5)
V <- matrix(rnorm(25),5,5)
LHS <- det(V)
RHS <- contract(o,V)
c(LHS=LHS,RHS=RHS,diff=LHS-RHS)
##          LHS          RHS         diff 
## 6.355108e+00 6.355108e+00 1.776357e-15

Above we see agreement to within numerical error.

Contraction from first principles

Suppose we wish to contract ฯ•=dxi1โˆงโ‹ฏโˆงdxik\phi=dx^{i_1}\wedge\cdots\wedge dx^{i_k} with vector ๐ฏ=(v1๐ž1,โ€ฆ,vk๐žk)\mathbf{v}=(v_1\mathbf{e}_1,\ldots,v_k\mathbf{e}_k). Thus we seek ฯ•๐ฏ\phi_\mathbf{v} with ฯ•๐ฏ(๐ฏ1,โ€ฆ,๐ฏkโˆ’1)=dxi1โˆงโ‹ฏโˆงdxik(๐ฏ,๐ฏ1,โ€ฆ,๐ฏkโˆ’1)\phi_\mathbf{v}\left(\mathbf{v}_1,\ldots,\mathbf{v}_{k-1}\right) = dx^{i_1}\wedge\cdots\wedge dx^{i_k}\left(\mathbf{v},\mathbf{v}_1,\ldots,\mathbf{v}_{k-1}\right). Writing ๐ฏ=v1๐ž1+โ‹ฏ+๐žk\mathbf{v}=v_1\mathbf{e}_1+\cdots+\mathbf{e}_k, we have

ฯ•๐ฏ(๐ฏ1,โ€ฆ,๐ฏkโˆ’1)=dxi1โˆงโ‹ฏโˆงdxik(๐ฏ,๐ฏ1,โ€ฆ,๐ฏkโˆ’1)=dxi1โˆงโ‹ฏโˆงdxik(v1๐ž1+โ‹ฏ+vk๐žk,๐ฏ1,โ€ฆ,๐ฏkโˆ’1)=v1dxi1โˆงโ‹ฏโˆงdxik(๐ž1,๐ฏ1,โ€ฆ,๐ฏkโˆ’1)+โ‹ฏ+vkdxi1โˆงโ‹ฏโˆงdxik(๐žk,๐ฏ1,โ€ฆ,๐ฏkโˆ’1).\begin{eqnarray} \phi_\mathbf{v}\left(\mathbf{v}_1,\ldots,\mathbf{v}_{k-1}\right) &=& dx^{i_1}\wedge\cdots\wedge dx^{i_k}\left(\mathbf{v},\mathbf{v}_1,\ldots,\mathbf{v}_{k-1}\right)\\&=& dx^{i_1}\wedge\cdots\wedge dx^{i_k}\left(v_1\mathbf{e}_1+\cdots+v_k\mathbf{e}_k,\mathbf{v}_1,\ldots,\mathbf{v}_{k-1}\right)\\&=& v_1 dx^{i_1}\wedge\cdots\wedge dx^{i_k}\left(\mathbf{e}_1,\mathbf{v}_1,\ldots,\mathbf{v}_{k-1}\right)+\cdots+ v_k dx^{i_1}\wedge\cdots\wedge dx^{i_k}\left(\mathbf{e}_k,\mathbf{v}_1,\ldots,\mathbf{v}_{k-1}\right). \end{eqnarray}

where we have exploited linearity. To evaluate this it is easiest and most efficient to express ฯ•\phi as โ‹€j=1kdxij\bigwedge_{j=1}^kdx^{i_j} and cycle through the index jj, and use various properties of wedge products:

dxi1โˆงโ‹ฏโˆงdxik=(โˆ’1)jโˆ’1dxijโˆง(dxi1โˆงโ‹ฏโˆงdxijฬ‚โˆงโ‹ฏโˆงdxiโˆ’k)=(โˆ’1)jโˆ’1kAltโก(dxijโŠ—(dxi1โˆงโ‹ฏโˆงdxijฬ‚โˆงโ‹ฏโˆงdxiโˆ’k))\begin{eqnarray} dx^{i_1}\wedge\cdots\wedge dx^{i_k} &=& (-1)^{j-1} dx^{i_j}\wedge\left(dx^{i_1}\wedge\cdots\wedge\widehat{dx^{i_j}}\wedge\cdots\wedge dx^{i-k}\right)\\ &=& (-1)^{j-1} k\operatorname{Alt}\left(dx^{i_j}\otimes\left(dx^{i_1}\wedge\cdots\wedge\widehat{dx^{i_j}}\wedge\cdots\wedge dx^{i-k}\right)\right) \end{eqnarray}

(above, a hat indicates a termโ€™s being omitted). From this, we see that lโˆ‰Lโ†’ฯ•=0l\not\in L\longrightarrow\phi=0 (where L={i1,โ€ฆik}L=\left\lbrace i_1,\ldots i_k\right\rbrace is the index set of ฯ•\phi), for all the dxpdx^p terms kill ๐žl\mathbf{e}_l. On the other hand, if lโˆˆLl\in L we have

ฯ•๐žl(๐ฏ1,โ€ฆ,๐ฏkโˆ’1)=(dxlโˆง(dxi1โˆงโ‹ฏโˆงdxlฬ‚โˆงโ‹ฏโˆงdxik))(๐žl,๐ฏ1,โ€ฆ,๐ฏkโˆ’1)=(โˆ’1)lโˆ’1k(dxlโŠ—(dxi1โˆงโ‹ฏโˆงdxlฬ‚โˆงโ‹ฏโˆงdxik))(๐žl,(๐ฏ1,โ€ฆ,๐ฏkโˆ’1))=(โˆ’1)lโˆ’1k(dxi1โˆงโ‹ฏโˆงdxlฬ‚โˆงโ‹ฏโˆงdxik)(๐ฏ1,โ€ฆ,๐ฏkโˆ’1)\begin{eqnarray} \phi_{\mathbf{e}_l}\left(\mathbf{v}_1,\ldots,\mathbf{v}_{k-1}\right) &=& \left(dx^{l}\wedge\left(dx^{i_1}\wedge\cdots\wedge\widehat{dx^{l}}\wedge\cdots\wedge dx^{i_k}\right)\right)\left(\mathbf{e}_l,\mathbf{v}_1,\ldots,\mathbf{v}_{k-1}\right)\\ &=& (-1)^{l-1}k\left(dx^{l}\otimes\left(dx^{i_1}\wedge\cdots\wedge\widehat{dx^{l}}\wedge\cdots\wedge dx^{i_k}\right)\right)\left(\mathbf{e}_l,\left(\mathbf{v}_1,\ldots,\mathbf{v}_{k-1}\right)\right)\\ &=& (-1)^{l-1}k\left(dx^{i_1}\wedge\cdots\wedge\widehat{dx^{l}}\wedge\cdots\wedge dx^{i_k}\right)\left(\mathbf{v}_1,\ldots,\mathbf{v}_{k-1}\right) \end{eqnarray}

Worked example using contract_elementary()

Function contract_elementary() is a bare-bones low-level no-frills helper function that returns ฯ•๐ฏ\phi_\mathbf{v} for ฯ•\phi an elementary form of the form dxi1โˆงโ‹ฏโˆงdxikdx^{i_1}\wedge\cdots\wedge dx^{i_k}. Suppose we wish to contract ฯ•=dx1โˆงdx2โˆงdx5\phi=dx^1\wedge dx^2\wedge dx^5 with vector ๐ฏ=(1,2,10,11,71)T\mathbf{v}=(1,2,10,11,71)^T.

Thus we seek ฯ•๐ฏ\phi_\mathbf{v} with ฯ•๐ฏ(๐ฏ1,๐ฏ2)=dx1โˆงdx2โˆงdx5(๐ฏ,๐ฏ1,๐ฏ2)\phi_\mathbf{v}\left(\mathbf{v}_1,\mathbf{v}_2 \right)=dx^1\wedge dx^2\wedge dx^5\left(\mathbf{v},\mathbf{v}_1,\mathbf{v}_2\right). Writing ๐ฏ=v1๐ž1+โ‹ฏ+v5๐ž5\mathbf{v}=v_1\mathbf{e}_1+\cdots+v_5\mathbf{e}_5 we have

ฯ•๐ฏ(๐ฏ1,๐ฏ2)=dx1โˆงdx2โˆงdx5(๐ฏ,๐ฏ1,๐ฏ2)=dx1โˆงdx2โˆงdx5(v1๐ž1+โ‹ฏ+v5๐ž5,๐ฏ1,๐ฏ2)=v1dx1โˆงdx2โˆงdx5(๐ž1,๐ฏ1,๐ฏ2)+v2dx1โˆงdx2โˆงdx5(๐ž2,๐ฏ1,๐ฏ2)+v3dx1โˆงdx2โˆงdx5(๐ž3,๐ฏ1,๐ฏ2)+v4dx1โˆงdx2โˆงdx5(๐ž4,๐ฏ1,๐ฏ2)+v5dx1โˆงdx2โˆงdx5(๐ž5,๐ฏ1,๐ฏ2)=v1dx2โˆงdx5(๐ฏ1,๐ฏ2)โˆ’v2dx1โˆงdx5(๐ฏ1,๐ฏ2)+0+0+v5dx1โˆงdx2(๐ฏ1,๐ฏ2)\begin{eqnarray} \phi_\mathbf{v}\left(\mathbf{v}_1,\mathbf{v}_2 \right) &=& dx^1\wedge dx^2\wedge dx^5\left(\mathbf{v},\mathbf{v}_1,\mathbf{v}_2\right)\\ &=& dx^1\wedge dx^2\wedge dx^5\left(v_1\mathbf{e}_1+\cdots+v_5\mathbf{e}_5,\mathbf{v}_1,\mathbf{v}_2\right)\\&=& v_1 dx^1\wedge dx^2\wedge dx^5\left(\mathbf{e}_1,\mathbf{v}_1,\mathbf{v}_2\right)+ v_2 dx^1\wedge dx^2\wedge dx^5\left(\mathbf{e}_2,\mathbf{v}_1,\mathbf{v}_2\right)\\ &{}&\qquad +v_3dx^1\wedge dx^2\wedge dx^5\left(\mathbf{e}_3,\mathbf{v}_1,\mathbf{v}_2\right)+ v_4 dx^1\wedge dx^2\wedge dx^5\left(\mathbf{e}_4,\mathbf{v}_1,\mathbf{v}_2\right)\\ &{}&\qquad\qquad +v_5dx^1\wedge dx^2\wedge dx^5\left(\mathbf{e}_5,\mathbf{v}_1,\mathbf{v}_2\right)\\&=& v_1 dx^2\wedge dx^5\left(\mathbf{v}_1,\mathbf{v}_2\right)- v_2 dx^1\wedge dx^5\left(\mathbf{v}_1,\mathbf{v}_2\right)+0+0+ v_5 dx^1\wedge dx^2\left(\mathbf{v}_1,\mathbf{v}_2\right) \end{eqnarray}

(above, the zero terms are because the vectors ๐ž3\mathbf{e}_3 and ๐ž4\mathbf{e}_4 are killed by dx1โˆงdx2โˆงdx5dx^1\wedge dx^2\wedge dx^5). We can see that the way to evaluate the contraction is to go through the terms of ฯ•\phi [that is, dx1dx^1, dx2dx^2, and dx5dx^5] in turn, and sum the resulting expressions:

o <- c(1,2,5)
v <- c(1,2,10,11,71)
(
(-1)^(1+1) * as.kform(o[-1])*v[o[1]] + 
(-1)^(2+1) * as.kform(o[-2])*v[o[2]] +
(-1)^(3+1) * as.kform(o[-3])*v[o[3]]
)
## An alternating linear map from V^2 to R with V=R^5:
##          val
##  1 5  =   -2
##  2 5  =    1
##  1 2  =   71

This is performed more succinctly by contract_elementary():

## An alternating linear map from V^2 to R with V=R^5:
##          val
##  1 5  =   -2
##  2 5  =    1
##  1 2  =   71

The โ€œmeatโ€ of contract()

Given a vector v, and kform object K, the meat of contract() would be

Reduce("+", Map("*", apply(index(K), 1, contract_elementary, v), elements(coeffs(K))))

I will show this in operation with simple but nontrivial arguments.

(K <- as.kform(spray(matrix(c(1,2,3,6,2,4,5,7),2,4,byrow=TRUE),1:2)))
## An alternating linear map from V^4 to R with V=R^7:
##              val
##  2 4 5 7  =    2
##  1 2 3 6  =    1
v <- 1:7

Then the inside bit would be

apply(index(K), 1, contract_elementary, v)
## [[1]]
## An alternating linear map from V^3 to R with V=R^7:
##            val
##  2 4 7  =    5
##  4 5 7  =    2
##  2 5 7  =   -4
##  2 4 5  =   -7
## 
## [[2]]
## An alternating linear map from V^3 to R with V=R^6:
##            val
##  1 2 6  =    3
##  2 3 6  =    1
##  1 3 6  =   -2
##  1 2 3  =   -6

Above we see a two-element list, one for each elementary term of K. We then use base Rโ€™s Map() function to multiply each one by the appropriate coefficient:

Map("*", apply(index(K), 1, contract_elementary, v), elements(coeffs(K)))
## [[1]]
## An alternating linear map from V^3 to R with V=R^7:
##            val
##  2 4 5  =  -14
##  2 5 7  =   -8
##  4 5 7  =    4
##  2 4 7  =   10
## 
## [[2]]
## An alternating linear map from V^3 to R with V=R^6:
##            val
##  1 2 3  =   -6
##  1 3 6  =   -2
##  2 3 6  =    1
##  1 2 6  =    3

And finally use Reduce() to sum the terms:

Reduce("+",Map("*", apply(index(K), 1, contract_elementary, v), elements(coeffs(K))))
## An alternating linear map from V^3 to R with V=R^7:
##            val
##  2 4 7  =   10
##  4 5 7  =    4
##  2 5 7  =   -8
##  1 2 3  =   -6
##  2 4 5  =  -14
##  1 3 6  =   -2
##  2 3 6  =    1
##  1 2 6  =    3

However, it might be conceptually easier to use magrittr pipes to give an equivalent definition:

K                                %>%
index                              %>%
apply(1,contract_elementary,v)       %>%
Map("*", ., K %>% coeffs %>% elements) %>%
Reduce("+",.)
## An alternating linear map from V^3 to R with V=R^7:
##            val
##  2 4 7  =   10
##  4 5 7  =    4
##  2 5 7  =   -8
##  1 2 3  =   -6
##  2 4 5  =  -14
##  1 3 6  =   -2
##  2 3 6  =    1
##  1 2 6  =    3

Well it might be clearer to Hadley but frankly YMMV.

References

Hankin, R. K. S. 2022. Stokesโ€™s Theorem in R. arXiv. https://doi.org/10.48550/ARXIV.2210.17008.
Spivak, M. 1965. Calculus on Manifolds. Addison-Wesley.
Weintraub, S. T. 2014. Differential Forms: Theory and Practice. Elsevier.