---
title: "Functions `contract()` and `contract_elementary()` in the `stokes` package"
author: "Robin K. S. Hankin"
output: html_vignette
bibliography: stokes.bib
link-citations: true
vignette: >
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteIndexEntry{contract}
  %\usepackage[utf8]{inputenc}
---

```{r setup, include=FALSE}
set.seed(0)
library("stokes")
library("spray")
library("disordR")
library("magrittr")
knitr::opts_chunk$set(echo = TRUE)
options(rmarkdown.html_vignette.check_title = FALSE)
knit_print.function <- function(x, ...){dput(x)}
registerS3method(
  "knit_print", "function", knit_print.function,
  envir = asNamespace("knitr")
)
```

```{r out.width='20%', out.extra='style="float:right; padding:10px"',echo=FALSE}
knitr::include_graphics(system.file("help/figures/stokes.png", package = "stokes"))
```

```{r,label=showcontract,comment=""}
contract
contract_elementary
```

To cite the `stokes` package in publications, please use
@hankin2022_stokes.  Given a $k$-form $\phi\colon
V^k\longrightarrow\mathbb{R}$ and a vector $\mathbf{v}\in V$, the
_contraction_ $\phi_\mathbf{v}$ of $\phi$ and $\mathbf{v}$ is a
$k-1$-form with

\[
  \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>1$; if $k=1$ we specify
$\phi_\mathbf{v}=\phi(\mathbf{v})$.  If @spivak1965 does discuss this,
I have forgotten it.  Function `contract_elementary()` is a low-level
helper function that translates elementary $k$-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.

```{r label=simpleexample}
(phi <- as.kform(1:5))
```

Thus $k=5$ and we have $\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

$$\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
$4$-form mapping $V^4$ to the reals with
$\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 $\mathbf{v}=(1,0,0,0,0)$ then

```{r label=contract10000}
v <- c(1,0,0,0,0)
contract(phi,v)
```

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

$$\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
$\mathbf{v}=(1,0,0,0,0)$).  Now consider $\mathbf{w}=(0,1,0,0,0)$:

```{r label=contract01000}
w <- c(0,1,0,0,0)
contract(phi,w)
```

$$\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:

```{r complicatedvectors}
contract(phi,c(1,3,0,0,0))
contract(phi,1:5)
```

We can check numerically that the contraction is linear in its vector
argument: $\phi_{a\mathbf{v}+b\mathbf{w}}=
a\phi_\mathbf{v}+b\phi_\mathbf{w}$.


```{r label=verifylinearityinv,cache=TRUE}
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)
```

We also have linearity in the alternating form:
$(a\phi+b\psi)_\mathbf{v}=a\phi_\mathbf{v} + b\psi_\mathbf{v}$.

```{r label=verifylinearityinphi}
(phi <- rform(2,5))
(psi <- rform(2,5))
a <- 7
b <- 13
v <- 1:7
contract(a*phi + b*psi,v) == a*contract(phi,v) + b*contract(psi,v)
```

## Contraction of products

@weintraub2014 gives us the following theorem, for any $k$-form $\phi$
and $l$-form $\psi$:

\[
\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=5$:

```{r label=verifycross}
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)
```

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

```{r,label=quitecomplicated}
summary(contract(phi^psi,v))
```

We may also switch $\phi$ and $\psi$, remembering to change the sign:

```{r, label=signswitch}
contract(psi^phi,v) ==  contract(psi,v) ^ phi + psi ^ contract(phi,v)
```


## Repeated contraction


It is of course possible to contract a contraction.  If $\phi$ is a
$k$-form, then $\left(\phi_\mathbf{v}\right)_\mathbf{w}$ is a $k-2$
form with

$$
\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:

```{r label=straight}
(phi <- rform(2,5))
u <- c(1,3,2,4,5,4,6)
v <- c(8,6,5,3,4,3,2)
contract(contract(phi,u),v)
```

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

```{r bothatonce,cache=TRUE}
M <- cbind(u,v)
contract(contract(phi,u),v) == contract(phi,M)
```

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

```{r verifydirect,cache=TRUE}
(o <- kform(spray(t(replicate(2, sample(9,4))), runif(2))))
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)
max(jj) - min(jj) # zero to numerical precision
```

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

```{r label=getazeroform}
contract(o,V)
```

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 $0$-form
(which is represented in the package as a `spray` with a zero-column
index matrix) we can use the `lost=FALSE` argument:

```{r doanothercontractnolose}
contract(o,V,lose=FALSE)
```

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

```{r,label=verifydeterminant}
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)
```

Above we see agreement to within numerical error.

## Contraction from first principles

Suppose we wish to contract $\phi=dx^{i_1}\wedge\cdots\wedge dx^{i_k}$
with vector $\mathbf{v}=(v_1\mathbf{e}_1,\ldots,v_k\mathbf{e}_k)$.
Thus we seek $\phi_\mathbf{v}$ with
$\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 $\mathbf{v}=v_1\mathbf{e}_1+\cdots+\mathbf{e}_k$, we have

\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  $\bigwedge_{j=1}^kdx^{i_j}$ and
cycle  through the  index $j$,  and  use various  properties of  wedge
products:

\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\not\in L\longrightarrow\phi=0$ (where $L=\left\lbrace
i_1,\ldots i_k\right\rbrace$ is the index set of $\phi$), for all the
$dx^p$ terms kill $\mathbf{e}_l$.  On the other hand, if $l\in L$ we
have

\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 $dx^{i_1}\wedge\cdots\wedge dx^{i_k}$.
Suppose we wish to contract $\phi=dx^1\wedge dx^2\wedge dx^5$ with
vector $\mathbf{v}=(1,2,10,11,71)^T$.

Thus we seek $\phi_\mathbf{v}$ with
$\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 $\mathbf{v}=v_1\mathbf{e}_1+\cdots+v_5\mathbf{e}_5$ we have

\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 $\mathbf{e}_3$ and
$\mathbf{e}_4$ are killed by $dx^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, $dx^1$, $dx^2$, and $dx^5$] in turn, and sum
the resulting expressions:


```{r,fromfirst}
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]]
)
```

This is performed more succinctly by `contract_elementary()`:


```{r, label=ceex}
contract_elementary(o,v)
```


## 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.


```{r,label=meatcontract}
(K <- as.kform(spray(matrix(c(1,2,3,6,2,4,5,7),2,4,byrow=TRUE),1:2)))
v <- 1:7
```

Then the inside bit would be

```{r,label=insidebitofmeat}
apply(index(K), 1, contract_elementary, v)
```

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:


```{r usemap}
Map("*", apply(index(K), 1, contract_elementary, v), elements(coeffs(K)))
```

And finally use `Reduce()` to sum the terms:

```{r usereduce}
Reduce("+",Map("*", apply(index(K), 1, contract_elementary, v), elements(coeffs(K))))
```

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

```{r usemagrittr}
K                                %>%
index                              %>%
apply(1,contract_elementary,v)       %>%
Map("*", ., K %>% coeffs %>% elements) %>%
Reduce("+",.)
```

Well it might be clearer to Hadley but frankly YMMV.

```{r tidyup, include=FALSE}
rm(phi)
```


## References

