\documentclass[11pt,a4paper]{article}
\usepackage[english]{babel}
\usepackage[utf8]{inputenc}
\usepackage{amsmath}
\usepackage{amsthm}
\usepackage{amsfonts}
\usepackage{bm}
\usepackage{stmaryrd}
\usepackage[hmargin=3cm,vmargin=2.4cm]{geometry}
\usepackage[dvipsnames]{xcolor}
\usepackage{hyperref}
\usepackage{fancyhdr}

\hypersetup{
  colorlinks=true,
  citecolor=NavyBlue,
  linkcolor=BrickRed,
  urlcolor=Violet
}

\pagestyle{fancy}
\fancyhead{}
\fancyhead[L]{\href{https://www-ljk.imag.fr/membres/Pierre.Karpman/cry\_adv2018\_codes.pdf}{https://www-ljk.imag.fr/membres/Pierre.Karpman/cry\_adv2018\_codes.pdf}}

\theoremstyle{definition}
\newtheorem{defi}{Definition}
\def\defiautorefname{Definition}
\newtheorem{example}{Example}
\def\defiautorefname{Example}

\renewcommand{\labelitemi}{---}

\DeclareMathOperator\code{\mathcal{C}}
\DeclareMathOperator\bigo{\mathit{O}}
\DeclareMathOperator\hd{\mathit{hd}}
\DeclareMathOperator\wt{\mathit{wt}}
\DeclareMathOperator\RM{\mathrm{RM}}
\DeclareMathOperator\fun{\mathit{F}}
\DeclareMathOperator\eval{eval}
\DeclareMathOperator\Ber{Ber}
\DeclareMathOperator\rank{rank}
\DeclareMathOperator\vspan{span}
\DeclareMathOperator\gl{GL}
\DeclareMathOperator\walsh{\mathcal{W}}
\DeclareMathOperator\proj{\pi}
\newcommand\ftwo{\mathbb{F}_{2}}
\newcommand\zo{\{0,1\}}
\newcommand\randraw{\xleftarrow{{\scriptscriptstyle\$}}}

\renewcommand{\thefootnote}{\fnsymbol{footnote}}

\title{Advanced cryptology (GBX9SY06)\\
$\ast$\\
Some coding theory aspects (useful) in cryptography}
\date{2018-11}
\author{Pierre Karpman}

\begin{document}

\maketitle{}

\section{First definitions; examples}

A \emph{linear code} of length $n$ and dimension $k$ over a field $\mathbb{K}$ is a $k$-dimensional subspace of $\mathbb{K}^n$. In these notes, we will focus on \emph{binary} codes, for which $\mathbb{K} = \mathbb{F}_2$, or possibly an extension thereof.\footnote{Consequently, we may take the liberty of
equating subtraction with addition in any formula or algorithm. In order to minimize the confusion, we will try to make this systematic. Nonetheless, most of the discussion seamlessly generalises to other (finite) fields.}
An important characteristic of a code is the \emph{minimum distance} (for the Hamming distance $\hd(\cdot,\cdot)$) $d$ between two (distinct) codewords. Define $\wt(\bm{x})$, $\bm{x} \in \ftwo^n$ as the number of
non-zero coordinates of $\bm{x}$; $\hd(\bm{x},\bm{y})$ as $\wt(\bm{x}+\bm{y})$. Then the minimum distance of a code $\code$ is $\min_{\bm{x}\in\code,\bm{y}\neq\bm{c}\in\code} \hd(\bm{x},\bm{y})$, which by linearity of $\code$ is equivalent
to $\min_{\bm{x}\neq\bm{0}\in\code} \wt(\bm{x})$. The main parameters of length, dimension, and minimum distance of a code $\code$ are summarized by saying that $\code$ is an $[n,k,d]_{\ftwo}$ code. While determining $n$ and $k$ is usually straightforward,
it is in general a hard problem to compute $d$.

\smallskip

Given a code $\code$, it will often be necessary to have an explicit \emph{encoding} map $\bm{x} \in \ftwo^k \mapsto \bm{x}' \in \code$ from \emph{messages} to \emph{codewords}. Such a map can be easily obtained by sampling $k$ linearly-independent codewords
$\bm{g}_{1},\ldots,\bm{g}_{k}$ and forming the \emph{generator matrix} $\bm{G}$ whose rows are the $\bm{g}_i$s.\footnote{Note that the we use the convention that vectors are \emph{row} vectors, if not specified otherwise.} The encoding map is then
simply $\bm{x} \mapsto \bm{x}\times\bm{G}$. One should remark that in general the matrix $\bm{G}$ (and thence the encoding map) will not be unique, as it depends on the selected codewords. A specific class of generator matrices are the ones in
\emph{systematic form}, corresponding to block matrices $\begin{pmatrix}\bm{I}_k & \bm{A}\end{pmatrix}$, where $\bm{I}_k$ is the $k$-dimensional identity matrix and $\bm{A}$ is a \emph{redundancy} block. A code always admits at least one systematic encoder, up to a permutation of the coordinates
of its codewords. One may be obtained by selecting $k$ linearly-independent columns of a generator matrix $\bm{G}$; applying a column permutation on $\bm{G}$ such that those columns are in the first $k$ positions; and computing the reduced row-echelon form
of $\bm{G}$. In other words, one obtains an encoder in systematic form by finding a permutation matrix $\bm{P}$ such that $\bm{GP}$ is of the form $\bm{G}' := \begin{pmatrix}\bm{G}_1 & \bm{G}_2\end{pmatrix}$ where $\bm{G}_1$ is invertible, and by computing
$\bm{G}_1^{-1}\bm{G}'$.

From the existence of a systematic encoder, one deduces that the largest possible minimum distance (or weight) of an $[n,k]$ linear code is $d_{\text{MDS}} = n-k+1$, which is a special case of the Singleton bound. Indeed, the maximum possible
weight of any row of a systematic encoder is 1 on the left (identity) block, and $n-k$ on the right (redundancy) block. A code reaching this bound is called \emph{maximum-distance separable}, or MDS.

Finally, note that for some codes, there may exist alternative encoders
that do not explicitly use a generator matrix.

\begin{example}[AES MixColumn]
	Let $\bm{M}$ be the matrix used in the MixColumn operation of the AES block cipher. The code generated by $\begin{pmatrix}\bm{I}_4 & \bm{M}\end{pmatrix}$ is an MDS code of paramateres $[8,4,5]_{\mathbb{F}_{2^8}}$.
\end{example}

\begin{example}[Binary Reed-Muller codes]
	The binary Reed-Muller code of order $r$ and in $m$ variables $\RM(r,m)$ is the vector-space formed by the multi-point evaluations of $m$-variate Boolean functions of degree $\leq r$ over $\ftwo^m$. In other words,
	a message is a Boolean function, and its associated (Reed-Muller) codeword is obtained by evaluating it over its entire domain.

	The codewords of this code have length $2^m$ and form a space of dimension $k := \sum_{i=0}^r\binom{m}{i}$.
	It can be shown that the minimum weight of any codeword is $2^{m-r}$~\cite[Ch. 13, Thm. 3]{MS}. The parameters of $\RM(r,m)$ are thus $[2^m, k, 2^{m-r}]_{\ftwo}$.

	A (non-systematic) encoding of a message can be efficiently computed by using a fast Möbius transform. Due to its involutive nature, the same transform can also be used to decode a codeword back to a message. However, this does not correct any
	error and on its own it is thus of rather limited use.

	Reed-Muller codes also follow a recursive ``$(u,u+v)$'' decomposition. One has that $\RM(r+1,m+1) = \{u||(u+v), u \in \RM(r+1,m), v \in \RM(r,m)\}$. This follows from the fact that an $(m+1)$-variate Boolean function $\fun(X_1,\ldots,X_{m+1})$
	of degree at most $r+1$ can be written as $\fun^{0}(X_1,\ldots,X_m) + X_{m+1}\fun^{1}(X_1,\ldots,X_m)$, with
	%where $\fun^{0}$ (resp. $\fun^1$) is obtained by partial evaluation of $\fun$ by taking $X_{m+1} = 0$ (resp. $X_{m+1} = 1$).
	$\deg(\fun^0) \leq r+1$ and $\deg(\fun^1) \leq r$. Furthermore, if we write $\fun^{0+}$ the $m+1$-variate function whose monomials are identical
	to $\fun^0$, $\fun^{1+}$ for % the $m+1$-variate function equal to
	$X^{m+1}\fun^{1}$,
	and $\vec{X}_{1,m}$ a given assignement for the indeterminates $X_1,\ldots,X_m$,
	then
	we always have:
	\begin{itemize}
		\item $\eval(\fun^0,(\vec{X}_{1,m})) = \eval(\fun^{0+},(\vec{X}_{1,m},0)) = \eval(\fun^{0+},(\vec{X}_{1,m},1))$;
		\item $\eval(\fun^1,(\vec{X}_{1,m})) = \eval(\fun^{1+},(\vec{X}_{1,m},1))$;
		\item $\eval(\fun^{1+},(\vec{X}_{1,m},0)) = 0$;
	\end{itemize}
	and the decomposition follows. Finally, one may notice that this is essentially the same induction as the one used in the fast
	Möbius transform algorithm.
\end{example}

\medskip

Given a code $\code$, it is often important to be able to determine if a vector of its ambient space is a codeword or not.
This may be done using a map $\bm{x} \mapsto \bm{y}$ s.t. $\bm{y}$ is ``zero'' iff. $\bm{x} \in \code$. One typically implements this with a \emph{parity-check matrix} $\bm{H} \in \ftwo^{n-k\times n}$
which is a basis of the (right) kernel of a generator matrix $\bm{G}$ of $\code$; the corresponding map, of codomain $\ftwo^{n-k}$, is then $\bm{x} \mapsto \bm{H}\times\bm{x}$. Equivalently, $\bm{H}$ is made of $(n-k)$ linearly-independent vectors of $\ftwo^n$ whose scalar product with any element of $\code$ is zero, and $\bm{H}\bm{G}^t$ and
$\bm{G}\bm{H}^t$ are both zero matrices. A parity-check matrix generates the \emph{dual} of $\code$, written $\code^\bot$, which is thence an $[n,n-k]$ code. A code that is its own dual is called \emph{self-dual}.

\section{Information set decoding}

In this section we focus on the problem of finding ``low-weight'' codewords of  a code, which is also essentially equivalent to finding a close-by codeword to a given vector, i.e. to decode.
This is generally a hard problem for codes that do not exhibit any particular structure (for instance if they are defined from a uniformly random generator matrix), as it is NP-hard~\cite{codeshard}, but efficient algorithms may exist for some specific codes. For now we will focus on
``inefficient'' generic algorithms that work for any code, but we will later present a good \emph{list decoder} for (punctured and shortened) first-order Reed-Muller codes.

\smallskip

Let $\code$ be an $[n,k,d]$ code for which $\bm{G}$ is a generator matrix. Enumerating all the codewords of $\code$ can trivially be done in time $2^k$ by multiplying $\bm{G}$ by all the vectors of $\ftwo^k$. This immediately allows to find
a weight-$d$ codeword of $\code$ or to decode to the (or one of the) closest codeword(s),
but the cost is quickly prohibitive.

A first remark on the way to find better alternatives is that the problem that one needs to solve usually does not require to enumerate all the codewords of $\code$. For instance, one may not need to find a codeword of \emph{minimum} weight,
but finding one of weight less than a known bound may be enough. Similarly, in a decoding context, one may know an upper-bound on the error weight. It is then possible to use a probabilistic algorithm that stops when a ``good-enough''
solution has been found.

A second remark is that the decoding problem can be solved by finding low-weight codewords; this will be used to justify the fact that we solely focus on algorithms for the latter. This can be explained in the following way: let
$\bm{c} \in \code$ be an initial codeword, and $\bm{\hat{c}} = \bm{c} + \bm{e}$ be a noisy codeword obtained by adding a noise $\bm{e}$ of weight $w \leq  \textit{max weight} <
\lfloor(d-1)/2\rfloor$. Then $\bm{c}$
is the unique closest codeword to $\bm{\hat{c}}$, and $\bm{e}$ is the unique vector of weight $w$ in the affine subspace $\bm{e} + \code$. Furthermore, this latter vector can be found by searching for a ``codeword'' of weight
$w$ in the code generated by $\begin{pmatrix}\bm{G}\\ \bm{\hat{c}}\\ \end{pmatrix}$. This codeword will even be unique, as for any $\bm{c}' \neq \bm{c} \in \code$, $\wt(\bm{c}' + \bm{\hat{c}}) = \wt(\bm{c}' + \bm{c} + \bm{e})
	\geq \wt(\bm{c}' + \bm{c}) - \wt(e) \geq d - \textit{max weight} > w$.

\medskip

The first probabilistic alternative to exhaustive search that we present is quite simple~\cite{Prange,McEliece}. Given $\bm{G}$, randomly select $k$ linearly-independent columns; this is called an \emph{information set}. Then permute these columns
to the first $k$ positions of $\bm{G}$, and compute the reduced row-echelon form (i.e. compute an alternative generator matrix $\bm{G}'$ in systematic form, associated to the selected information set).
Finally, check if any of the resulting $k$ rows have a weight less than the input bound. The idea behind this algorithm is that any row of the obtained systematic encoder has by definition a very low weight of exactly one on its first $k$ positions,
and the weight on the remaining $n-k$ positions depends on a random codeword linear combination. One then hopes that for \emph{some} information sets, the weight on these latter positions we also be small, resulting in an overall low-weight codeword.
In other words, the algorithm will return a weight-$w$ codeword after examining a given information set if it is s.t. there is a codeword of weight 1 over the information set and of weight
$w - 1$ over its complement, the \emph{redundancy set}.

There is also a simple interpretation of this algorithm if one directly thinks of it in a ``decoding'' sense. An information set is by definition a set of positions that carries enough information to fully determine the message corresponding to a codeword.
Indeed, given the value of a codeword on an information set, one can reconstruct the entire (non-noisy) codeword by simply applying an encoder systematic w.r.t. this set;
it is then easy to invert the encoding to go back to the original message.
Thus, what the above does is (randomly) trying to find an information set over which the error vector is all-zero.

\smallskip

A variant of the above first algorithm due to Lee and Brickell~\cite{LeeBrickell} consists, for each information set, in checking the weight of all linear combinations of rows of $\bm{G}'$ of weight less than a small value $p$ (typically 2 or 3). This somehow amortizes
the cost of computing $\bm{G}'$ by considering more codewords for each matrix, as now the algorithm returns on a given information set if
a codeword's weight splits as $(i,w-i), 1 \leq i \leq p$ over itself and its complement.
Also, note that for binary codes, computing all of these can be done particularly efficiently by using Gray codes.

\smallskip

Another variant due to Leon aims to reduce the practical cost of checking the weight of a codeword, and is thus mostly useful for long codes. The idea is simply to first check
if a codeword generated from the above procedure has a small weight on a few positions (i.e. to first consider a short \emph{punctured} code), and only to look at the entire
codeword in that case. For instance, if one requires the punctured codeword to have weight zero on its redundancy set of size $l$, one is in effect searching for codewords
whose weight splits as $(i,0,w-i), 1 \leq i \leq p$ over the information set, and the non-punctured (resp. punctured) redundancy positions.

While this approach looks at fewer candidates per information set as the Lee-Brickell algorithm, this is hoped to be counter-balanced by more efficient implementations.

\medskip

Another algorithm due to Stern still follows the overall same approach, but improves the time complexity at the cost of some memory~\cite{Stern}. The key idea is to split the search
space into two lists and to exploit collisions to obtain a quadratic speed-up at some stage of the search. Starting from the initial algorithm, one splits the information set
into two subsets $I_1$ and $I_2$, and forms the lists $\Lambda_1$ and $\Lambda_2$ of codewords of weight less than $p$ on each subset respectively. Then one only fully checks the weight of
codewords formed by the sum of elements of $\Lambda_1$ and $\Lambda_2$ that are identical over $l$ prescribed positions $Z$ of the redundancy set. One is then searching for codewords
whose weight splits as $(i,j,0,w-i-j), 1 \leq i,j \leq p$ over $I_1$, $I_2$, $Z$ and the remainder.

It is essential to notice that for a given information set, checking for each of the $\#\Lambda_1\#\Lambda_2$ candidate codewords if it is of the above form indeed takes a cost
linear in $\#\Lambda_{1,2}$ (by using an appropriate data structure).

Finally, one may remark that this algorithm takes more input parameters than the previous ones. This, together with the fact that it is not memory-less may make it harder to
determine what parameter choice is best suited to a given code.

\medskip

An important observation made by Canteaut and Chabaud~\cite{CanteautChabaud} is that the most expensive step in the above algorithms is the computation of the systematic encoder for a given
information set. They then suggest that instead of selecting a new independent information set at every iteration, one may ``update'' the current set by randomly replacing one
of its columns by one column of the redundancy set, which is much more efficient. Furthermore, one can easily be convinced that after a few iterations, the obtained information
set will be essentially independent from the starting one, hence there is no risk that one gets stuck in a small subset of the search space considered by the other algorithms.

We will conclude by describing how to efficiently update an information set. Let $\bm{G} = \begin{pmatrix}\bm{I} & \bm{A}\end{pmatrix}$ be a systematic generator matrix;
our objective is to compute $\bm{G}' = \begin{pmatrix}\bm{I} & \bm{A}'\end{pmatrix}$ which is a generator matrix for the same code and equal to the
reduced row-echelon form of a matrix obtained from $\bm{G}$ by swapping one column $\bm{I}_{\cdot,i}$ of the identity with one column
$\bm{A}_{\cdot,j}$ of the redundancy matrix. First notice that this latter
process only results in a systematic matrix if $\bm{A}_{\cdot,j}$ is linearly independent from $\bm{I}\backslash\bm{I}_{\cdot,i}$, which is equivalent to
requiring that $\bm{A}_{i,j} = 1 \neq 0$. Second, the matrix $\bm{A}'$ is simply obtained from $\bm{A}$ by adding the row $\bm{A}_{i}$
to every row $\bm{A}_{i'}$ where $\bm{A}_{i',j} = 1$. Indeed, this corresponds to the ``reduction'' step one needs to perform after swapping the above two columns.

\section{Learning Parity with Noise cryptosystems}

In this section, we introduce the \emph{Learning Parity with Noise} (LPN) problem, and its application to the design of cryptosystems.
The LPN problem is rather attractive because of its very concise description. Let $\bm{s} \in \ftwo^k$, $\bm{a} \randraw \ftwo^k$,
$e \leftarrow \Ber_\eta$, where $\Ber_\eta$ is the Bernoulli distribution of parameter $\eta$; that is,
$\Pr[e = 1] = \eta$. Then the LPN problem is to guess the value of the
scalar product $\bm{s} \cdot \bm{a}$ when given $(\bm{a},\bm{s} \cdot \bm{a} + e)$. An algorithm is then said to solve
this problem with advantage $\varepsilon$ if it answers correctly with probability $p$ and $2|p - 1/2| = \varepsilon$.

It is clear that if $\eta = 1/2$, the distribution of $\bm{a}\cdot\bm{s} + e$ is independent of $\bm{a}$ and $\bm{s}$, and no algorithm
can succeed with a non-zero advantage. Similarly, without prior knowledge about $\bm{s}$ and except when $\bm{a} = \bm{0}$, one cannot hope
to solve the problem given a \emph{single} query of the above form, even in the absence of noise (i.e. even when $\eta = 0$). It is then natural to extend the problem to arbitrarily-many queries $q$, where
the unknown (secret) vector $\bm{s}$ is kept constant. One may then reformulate the problem as letting $\bm{A} \randraw \ftwo^{k\times q}$,
$\bm{e} \leftarrow \Ber_{\eta,q}$ (that is, each bit of $\bm{e} \in \ftwo^q$ has independent probability $\eta$ to be equal to one), and asking to distinguish
$(\bm{A},\bm{s}\bm{A} + \bm{e})$ from $(\bm{A}, \bm{u})$ where $\bm{u} \randraw \ftwo^q$. An algorithm is then said to $(t,q)$ solve LPN$_{k,\eta}$ with advantage $\varepsilon$
if it makes $q$ queries and has running time $t$.

It is now again clear that if $\eta = 0$, the problem is trivially solvable as long as $\rank(\bm{A}) = k$, as it is then enough to identify $k$ linearly-independent columns of $\bm{A}$ to recover
$\bm{s}$ (which then allows to predict all the other queries with advantage 1). In the more meaningful case where $\eta > 0$, recovering $\bm{s}$ becomes equivalent to decoding a noisy codeword for
some random code of length $q$ (which is a parameter that may be chosen by the solving algorithm).
We will discuss this matter in more details in the next section, and focus for now on some LPN-based cryptosystems whose security depends on the computational hardness of this problem.

\bigskip

We first describe LPN-C, which is a family of symmetric encryption schemes defined by Gilbert et al.~\cite{LPNC}. While the confidentiality of an LPN-C instance reduces to the hardness of a corresponding
LPN problem, the scheme is inherently malleable and must thus be used in conjunction with a MAC.

An LPN-C instance is parameterized by a random code length $k$, a noise level $\eta$, a message length $r$, and the parameters of an $[m,r,d]$ binary code $\code$, assumed to be efficiently decodable up to $w$ errors and s.t. $\Pr[\wt(\bm{e}) > w : \bm{e} \leftarrow \Ber_{\eta,m}]$
is small.
The scheme works as follows. The sender and the receiver first share a secret random matrix $\bm{M} \in \ftwo^{k\times m}$. Then, to encrypt an $r$-bit message $\bm{x}$, the sender
draws a vector $\bm{a} \randraw \ftwo^k$ and $\bm{e} \leftarrow \Ber_{\eta,m}$, computes $\bm{y} = \code(\bm{x}) + \bm{a}\bm{M} + \bm{e}$, and sends $(\bm{a},\bm{y})$ to the receiver.
To decrypt, the receiver computes $\hat{\bm{x}} = \bm{y} + \bm{a}\bm{M} = \bm{x} + \bm{e}$, and uses the decoder of $\code$ to recover $\bm{x}$.
The designers of LPN-C proposed some parameters for secure instantiations of LPN-C, but without specifying which code $\code$ to choose. An example is to take $k = 768$, $m = 160$, $\eta = 1/20$, $r = 75$, $d = 25$.

An informal way to argue about the security of this scheme is that if one uses parameters for which the LPN problem is hard to solve, then the (encoded) message $\code(\bm{x})$ is whitened
by a pseudo-random mask $\bm{a}\bm{M} + \bm{e}$ which is hard to distinguish from random, and is thus encrypted by a secure ``stream cipher''.\footnote{There is a slight difference between this
setting and the actual definition of LPN that we have used: here the \emph{matrix} is secret and the vector $\bm{a}$ is public. Yet, this simply corresponds to $m$ ``single'' queries for $m$
independent secrets batched together into $\bm{M}$.}

\medskip

We now turn to an LPN-based public-key cryptosystem due to Alekhnovich~\cite{alekh03}. This is a highly impractical design, as it only encrypts a single bit and the decryption of ``1'' fails with
probability 1/2. It is however of theoretical interest, and is rather simple to describe.

This scheme is parameterized by an integer $n$, from which one derives $m = 2n$, $k = n^{1/2-\epsilon}$, $\eta = k/n$. The public key is a matrix $\bm{A}' \in \ftwo^{n+1\times m}$, generated
as $\begin{pmatrix}\bm{A}\\ \bm{\hat{c}}\\ \end{pmatrix}$ where $\bm{A} \randraw \ftwo^{n\times m}$,  $\bm{\hat{c}} = \bm{x}\bm{A} + \bm{e}$,   $\bm{x} \randraw \ftwo^n$, $\bm{e} \leftarrow \Ber_{\eta,m}$.
The private key is the vector $\bm{e}$.
In other words, one defines a random $[2n,n]$ code with generating matrix $\bm{A}$ and augments it with a low-weight codeword $\bm{e}$ to the code $\code$ generated by $\bm{A}'$. One can indeed check
that $\bm{e}$ is in the span of $\bm{A}'$, as it is equal to $\begin{pmatrix} \bm{x} & 1 \end{pmatrix}\bm{A}'$.\footnote{One may also remark that for the chosen parameters, $\bm{e} \notin \vspan(\bm{A})$
with high probability. In the unlikely event where this would be the case, one can simply choose another vector and try again.}
This augmentation is however done in a ``hidden'' way, as recovering $\bm{e}$
from $\bm{A}'$ is a (hard) decoding problem for the code defined by $\bm{A}$.

To encrypt one bit for the public key $\bm{A}'$, the sender proceeds as follows. To encrypt the bit 1, it sends a vector $\bm{\alpha_1} \randraw \ftwo^m$. To encrypt the bit 0,
it computes and sends $\bm{\alpha_0} = \bm{d} + \bm{e}'$, where $\bm{e}' \leftarrow \Ber_{\eta,m}$ and $\bm{d}$ is a uniformly random element of $\code^\bot$.
The receiver computes the decryption of $\bm{\alpha}$ as $\bm{\alpha} \cdot \bm{e}$.

It is quite immediate to see that upon receiving $\bm{\alpha_1}$, decryption will fail with probability 1/2. It is a bit less obvious that the decryption
of $\bm{\alpha_0}$ is more successful. In that case, the receiver computes $\bm{\alpha_0} \cdot \bm{e} = \bm{d}\cdot\bm{e} + \bm{e}\cdot\bm{e}'$. Because
$\bm{d} \in \code^\bot$, $\bm{e} \in \code$, this simplifies to $\bm{e}\cdot\bm{e}'$. Finally, the probability that $\bm{e}$ and $\bm{e}'$ have a non-disjoint
support is $\approx (1-\eta)^k$, which is negligible.

We already have informally argued that computing the secret key $\bm{e}$ from the public key $\bm{A}'$ reduces to a hard decoding problem. We may now also
remark that distinguishing $\bm{\alpha_0}$ from $\bm{\alpha_1}$ reduces to an LPN-like problem for a generating matrix of $\code^\bot$. Alekhnovich then
showed that the latter is computationally indistinguishable from a random matrix, which allows to conclude the reduction to LPN.

\medskip

We conclude this overview by describing an LPN-based symmetric authentication protocol named Lapin~\cite{lapin}. Strictly speaking, Lapin is based on the Ring-LPN variant
of the problem, whose aim is to decrease the communication complexity. We will first describe it in the LPN framework, and will address this difference next.

A challenger and a verifier share two secret vectors $\bm{s}$, $\bm{s}' \in \ftwo^n$. To authenticate the challenger, the verifiers draws $\bm{C} \randraw \ftwo^{n\times n}$
and send it to the former. The challenger then draws $\bm{R} \randraw \gl(n, \ftwo)$, $\bm{e} \leftarrow \Ber_{\eta,n}$, and sends $(\bm{R}, (\bm{s}\bm{C} + \bm{s}')\bm{R} + \bm{e})$.
The verifier then recovers $\bm{e}$ and validates the challenge if it is of weight less than $\mu \eta n$ for some small acceptance threshold $\mu$.

The Ring-LPN variant of the protocol works similarly, but works over rings of the form $\ftwo[X]/\langle f \rangle$, for some polynomial $f$. The challenge matrix $\bm{C}$
is replaced by a ring element $\proj(c)$, $c \in \ftwo^\lambda$, where $\proj$ is a mapping verifying some conditions; the matrix $\bm{R}$ is replaced by an invertible
element of the ring $r$; the secrets are now also ring elements $s$ and $s'$, and so is the error $e$ (still drawn from a Bernoulli distribution, when seen as a vector).
The message sent by the challenger is then simply $(r, (s\proj(c) + s')r + e)$. The advantage of this variant over the matrix version of the protocol is that the ring elements
(esp. $r$) have a much more compact representation of size $\approx n$ than $n\times n$ random matrices, which essentially decreases the communication cost by a factor $n$.
However, the security now depends on the hardness of decoding codes posessing some structure, which may allow for more efficient algorithms.

Finally, a (still informal) way to argue about the security of this protocol is to notice that the challenger's answer can be written (in the original LPN case) as $\bm{s}\bm{CR} + \bm{s}'\bm{R} + \bm{r}$.
In other words, one is masking the string challenge-and-secret-dependent string $\bm{sCR}$ by an LPN query $\bm{s}'\bm{R} + \bm{e}$, which is by assumption computationally indistinguishable from random.

\section{LPN solving algorithms}

It is quite clear that solving an LPN instance can be done by using generic decoding algorithms. Indeed, one may simply try to solve the \emph{search} variant of the LPN problem, which on $\bm{sA} + \bm{e}$ tries to
recover $\bm{s}$, i.e. tries to decode a noisy codeword of the code generated by $\bm{A}$. As solving the search problem allows to solve the distinguishing problem as well, the hardness of LPN
is not more than the one of decoding.

One subtlety that we already mentioned is that an LPN-solving algorithm is free to choose the number of oracle queries it wants to use, i.e. the \emph{length} of the code it wishes to decode. Surely this has
to be sufficiently large to even make a successful decoding possible (for instance one may try to ensure that there is at least one error-free information set w.h.p.), but there is no similar constraint
on the maximum number of queries.

We will now describe an algorithm that specifically exploits the ability to make many queries in an LPN problem to decrease the time complexity
for solving the (search) problem (at the cost of a potentially huge increase in memory and query complexity). This algorithm was first described in this context by Blum, Kalai and Wasserman~\cite{BKW},
and is traditionally called \emph{BKW}; we heavily base our presentation on the variant described by Bernstein and Lange~\cite{bunny}.

\smallskip

We first describe a simple variant of BKW. An intuition behind the algorithm is that given sufficiently many LPN queries $\bm{s}\cdot\bm{a} + e$ with a \emph{fixed}, vector $\bm{a}$,
one can efficiently recover one bit of $\bm{s}$ (viz. $\bm{s}\cdot\bm{a}$) using a majority vote among the samples. This requires $\approx c^{-2}$ samples, where $c = 1 - 2\eta$ ($0 < \eta < 1/2$)
is the correlation
of the noise. Repeating this process for $k$ linearly independent masks $\bm{a}$ leads to a full recovery of $\bm{s}$.

Of course, as the masks $\bm{a}$ of LPN queries are uniformly random, collecting enough samples for a single one is equivalent to observing a $c^{-2}$-multi-collision, which
requires $2^{k\cdot(c^{-2}-1)/c^{-2}} \approx 2^k$ samples, so this does not really improve on the exhaustive search of the secret. The idea behind BKW is then to artificially
create samples of the above form by combining many random ones; this may provide enough samples to apply majority decoding, but each sample is now ``noisier'', and one must then find
a proper tradeoff.

More precisely, BKW proceeds as follows. To solve an instance of LPN with noise level $\eta$ (i.e. noise correlation $c = |1-2\eta|$) and dimension $k$ with $q$ queries making a pool $P_0$, we start by fixing
a block-size parameter $b$. Then one creates a table $T$ of size $2^b$ and an updated pool $P_1$, both initially empty. Next, for each sample  $x = (\bm{a}, v = \bm{a}\cdot\bm{s} +e) \in P$, do the following:
\begin{enumerate}
	\item Call $i$ the integer value corresponding to the last $b$ bits of $\bm{a}$. If $T[i] = \emptyset$, update it as $T[i] \mapsfrom (\bm{a}, v)$.
	\item Else, retrieve $(\bm{a}',v')$ from $T[i]$ and update $x$ as $(\bm{a},v) \mapsfrom (\bm{a}+\bm{a}',v+v')$ and store it in $P_1$.
\end{enumerate}
At the end of this process, and provided that $q \gg 2^b$, there are $q-2^b$ samples in $P_1$ which all have their masks $\bm{a}$ equal to zero on their last $b$ bits. However, the corresponding dot
products indeed became noisier, the correlation $c$ having been squared.\footnote{One can check that the probability of error is $2\eta(1-\eta) = 2(\eta-\eta^2)$, giving a correlation
$1-4(\eta-\eta^2) = (1-2\eta)^2$.} One may now clear the table $T$ of all its entries and start this process again, obtaining a pool $P_2$ of $q - 2^{b+1}$ samples with masks whose
$2b$ last bits are zero and noise correlation $c^4$, etc., up to a pool $P_t$ of $q - t2^b$ samples with masks with $tb$ zeroes and noise correlation $c^{2^t}$.

The original algorithm chooses $t$ and $b$ s.t. $tb = k-1$, that is the last pool is made of samples with masks $\bm{a}$ of the form $(0,\ldots,0)$, which are useless, and $(1,0,\ldots,0)$,
whose corresponding $v$ values can be used to find the first bit of $\bm{s}$ by a majority vote. This latter step will succeed w.h.p. if $\#P_t \approx c^{-2^{t+1}}$. Finally, the remaining
bits of $\bm{s}$ are iteratively retrieved using the same process.

\medskip

We now introduce a first optimization to the above algorithm, due to Levieil and Fouque~\cite{LF}. Let $l$ be a second ``block size'' parameter, corresponding to an exhaustive search step.
The goal is to guess $\bm{s}$ by blocks of $l$ bits instead of blocks of 1; the first iteration of BKW is then stopped at $tb = k -l$. At that point, one has $N := q - t2^b$ noisy
dot products with masks $\bm{a}_i$ whose $l$ first bits only may be non-zero.\footnote{Note that as $N \gg l$, \emph{non-noisy} approximations would indeed allow to uniquely recover the first $l$ bits of $\bm{s}$.}
Let $\bm{y} \in \ftwo^k$ be a vector whose $k-l$ last bits are zero; the idea is to notice that if $\bm{y}$ and $\bm{s}$ are equal on their first $l$ bits, then $\bm{a}_i \cdot \bm{y} = \bm{a}_i \cdot \bm{s}$.
Consequently, for every sample $(\bm{a}_i, v_i)$, one has $\bm{a}_i \cdot \bm{y} + v_i = e_i$ (where $e_i$ is an aggregated error of some correlation $c'$ coming from the pool creation) which is then
one with non-uniform probability $(1-c')/2$. On the other hand,
if $\bm{y}$ and $\bm{s}$ disagree on their last bits, $\bm{a}_i \cdot \bm{y} + v_i = \bm{a_i} \cdot (\bm{y} + \bm{s}) + e_i$ for a non-zero term $\bm{y}+\bm{s}$, and from the uniformity of $\bm{a}_i$ on its first $l$ bits,
this expression is one with probability $1/2$ exactly. If $N \approx c'^{-2}$, it is thus possible to distinguish the value of $\bm{y}$ that matches $\bm{s}$ to recover its last $l$ bits.

%The full process for one block of size $l$ is as follows:
%\begin{enumerate}
%	\item For all $2^l$ candidates $\bm{y_i}$ for the first $l$ bits of $\bm{s}$, compute $\walsh(\bm{y}_i) = \sum_{j = 0}^N (-1)^{\bm{a}_j\cdot\bm{y}_i+v_j}$ (where the sum is over $\mathbb{Z}$).
%	\item Return the $\bm{y}_i$ for which $|\walsh(\bm{y}_i)|$ is largest.
%\end{enumerate}
%A key algorithmic observation is that if $N$ equals $2^m$ a power of two, the above corresponds to the computation of the Hadamard-Walsh spectrum of the Boolean function $i \mapsto v_i$, which can be done
%efficiently thanks to a fast-fourier-like process in time $m2^m$, using the recursion:
%\[
%	\walsh(\bm{y}) = \sum_{j = 0}^{2^m} (-1)^{\bm{a}_j\cdot\bm{y}+v_j}% = \walsh(\bm{
%\]

The full process for one block of size $l$ is as follows:
\begin{enumerate}
	\item For all $2^l$ candidates $\bm{y_i}$ for the first $l$ bits of $\bm{s}$, compute $\hat{c}_{\bm{y}_i} = \sum_{j = 0}^N (-1)^{\bm{a}_j\cdot\bm{y}_i+v_j}$ (where the sum is over $\mathbb{Z}$).
	\item Return the $\bm{y}_i$ for which $|\hat{c}_{\bm{y}_i}|$ is largest.
\end{enumerate}
A key algorithmic observation is that, the above is similar to the computation of the Hadamard-Walsh spectrum of the Boolean function $i \mapsto v_i$, for which fast algorithms exist. One can indeed
remark that for $\bm{y}$ and $\bm{y}'$ that differ only on a single bit, on position $l$ (w.l.o.g.), if we write $\bm{y}''$ the common part of the two vectors of length $l-1$ and
$\bm{a}_j^1$ (resp. $\bm{a}_j^0$) the masks whose $l^\text{th}$ bit is 1 (resp. 0); then we have the following; let
\begin{align*}
A := \sum_{\bm{a}_j^1}(-1)^{\bm{a}_j\cdot\bm{y}+v_j}, \qquad&
\quad B := \sum_{\bm{a}_j^0}(-1)^{\bm{a}_j\cdot\bm{y}''+v_j}, &
-A = \sum_{\bm{a}_j^1}(-1)^{\bm{a}_j\cdot\bm{y}'+v_j},
\end{align*}
and	$\hat{c}_{\bm{y}} = A + B$, $\hat{c}_{\bm{y}'} = -A + B$, and this equality can be applied $l$ times recursively.

\medskip

The above idea can be further optimized by first applying a transformation on the LPN samples in order to reduce the problem to finding an equivalent \emph{low weight} secret. This idea is due
to Kirchner~\cite{paul_lpn} and works as follows. Denote $\bm{A}$ the full matrix of the masks of $q$ queries to an LPN oracle of dimension $k$. Let $\bm{A}_1$ be an information set of $k$
linearly-independent columns of $\bm{A}$, and let $\bm{A}_2$ denote $k$ columns of $\bm{A}$ not in $\bm{A}_1$. The key observation is that the sum $(\bm{s}\bm{A}_1 + \bm{e}_1)\bm{A}_1^{-1}\bm{A}_2
+ \bm{s}\bm{A}_2 + \bm{e}_2$ simplifies to $\bm{e}_1\bm{A}_1^{-1} + \bm{e}_2$. In other words, one can transform the $2k$ queries w.r.t. masks $\bm{A}_1$ and $\bm{A}_2$ for the secret $\bm{s}$
into $k$ queries for the ``secret'' $\bm{e}_1$, whose expected Hamming weight is given by the noise level $\eta$, and is then strictly less than $k/2$. The same transformation can
be applied many times by changing $\bm{A}_2$, and one can then run a BKW algorithm to retrieve $\bm{e}_1$ (which obviously immediately leads to $\bm{s}$). There is no particular advantage
in using this transformation when applying the original BKW algorithm, but when one guesses the secret by block, it becomes enough to guess secrets of low weight, which reduces
the search space. Finally, note that the joint computation of the many correlations $\hat{c}_{\bm{y}}$ can still be done efficiently for those sparse secrets.

\medskip

We conclude this section by mentioning that even prior the publication of the BKW algorithm, Bleichenbacher described a similar algorithm to exploit biased DSA signatures~\cite{bleichenbacher, ac14bleich}.
Recall that in a DSA (or Schnorr) signature, the signer provides a pair $(c,r+cx)$, where $x$ is a secret exponent, $c$ is random and depends on the message to be signed, and $r$ is
a random mask. If $r$ is uniform, the secret $cx$ is blinded by a one-time-pad and nothing can be learned. But if $r$ is biased, then one obtains a problem of noisy decoding similar
to LPN. Bleichenbacher's algorithm to retrieve $x$ from many signatures consists in combining samples to zero some of their bits, and to apply a fast Fourier transform to recover,
say, 40 bits of the secret. The original bias exploited by Bleichenbacher was a small modulo bias: instead of taking $r$ uniformly in $\mathcal{S}$, $\#\mathcal{S} \approx 2^{160}$,
it was uniform over $[0,\ldots,2^{160}-1]$ and then reduced modulo $\#\mathcal{S}$.
Finally, we remark that similarly to information-set decoding style algorithms, one can also recover a DSA secret using fewer sample signatures with biased masks by finding short vectors in
a Euclidean lattice~\cite{howgrave_grahm_Smart_dsa}.

\section{The Goldreich-Levin theorem}

In this section, we present the application of list decoding to a proof of existence of \emph{hard-core} predicates for one-way functions, due to Goldreich and Levin~\cite{gl}. Informally,
the objective is to show that if $\fun : \zo^n \rightarrow \zo^m$ is a one-way function, in the sense that it is hard to find a preimage $x$ given $\fun(x)$, then it is also hard to predict
(with probability significantly away from $1/2$)
the value $\bm{a}\cdot\bm{x}$, for any $\bm{a} \in \ftwo^n$ (where $\bm{x} \in \ftwo^n$ is the canonical embedding of $x \in \zo^n$). A possible proof is to show that if one is given
a prediction oracle for (several) $\bm{a}\cdot\bm{x}$ with correlation $c$, then one can reconstruct $x$ w.h.p. with time and memory complexity $c^{-2}$. Thus, if ``it costs at least $T$
to invert $\fun$'', one has that it is impossible to predict the value of any of the above predicates with correlation ($\approx$ advantage) better than $1/\sqrt{T}$.

One first subtlelty that deserves to be mentioned is that because of the nature of the result we want to prove, the predicate oracle that we will use to invert $\fun$ can
only be called \emph{once} for a given mask $\bm{a}$: indeed, it makes no sense to define different predictions $\bm{a}\cdot\bm{x}$ several times as $\bm{x}$ itself is fixed. On the other
hand, the mask $\bm{a}$ can be chosen freely. This is to be contrasted with, say, an LPN setting, where one is given $\bm{a}\cdot\bm{s}$ for random masks $\bm{a}$, that may
still be potentially equal, in case of unlikely collisions. Yet in either case the effect is the same: it is not (by definition or computationally) feasible to recover $\bm{x}$ by recovering enough
biased predictions or samples with the same $n$-linearly independent masks. Finally, an accurate modelisation of the oracle's power in terms of codes is to say that if $\bm{A} \in \ftwo^{n\times q}$
has $q$ pairwise distinct columns, querying a prediction oracle with $q$ masks $\bm{A}$ and advantage $\varepsilon$ (meaning that a prediction is correct with probability
$p > 1/2$; $2p - 1 = \varepsilon$) is equivalent to obtaining a noisy codeword $\bm{x}\bm{A} + \bm{e}$ with $\bm{e} \leftarrow \Ber_\eta,q$, $\eta = 1/2 - \varepsilon/2$.

A second essential remark is that $\fun$ and $\fun(x)$ are both known to the adversary. Thus, as soon as one knows a ``small'' list $L$ that contains $x$ w.h.p., one can recover
$x$ uniquely by mapping $\fun$ to $L$ and comparing the result to $\fun(x)$ (here we assume that $L$ does not contain collisions for $\fun$, which is true w.h.p. if $L$ is small).

Putting the two remarks together, what we need is an efficient \emph{list-decoding} algorithm for a code generated by a matrix $\bm{A}$ with pairwise-distinct columns. In the remainder,
we will give exactly such an algorithm for a punctured \emph{Hadamard code} (that is a first-order Reed-Muller code where messages have no constant term).

% objective, constraints

% list-decoding of punctured RM/Hadamard

\bibliographystyle{alpha}
\bibliography{tehbib}

\end{document}


\documentclass[10pt]{article}

\usepackage[latin1]{inputenc}
\usepackage{amsmath, amssymb, amsfonts, amsthm}
\usepackage{upgreek}
\usepackage{amsthm}
\usepackage{fullpage}
\usepackage{graphicx}
\usepackage{cancel}
\usepackage{subfigure}
\usepackage{mathrsfs, bbold}
%\usepackage{outlines}
\usepackage[font={sf,it}, labelfont={sf,bf}, labelsep=space, belowskip=5pt]{caption}
\usepackage{hyperref}
%\usepackage{minted}
\usepackage{xcolor}

\usepackage{fancyhdr}
\usepackage[title]{appendix}

\DeclareMathOperator{\sgn}{sgn}
\DeclareMathOperator{\tr}{tr}

\pagestyle{fancy}
\headheight 24pt
\headsep    12pt
\lhead{Tensor Flattening}
\fancyfoot[C]{} % hide the default page number at the bottom
\lfoot{}
\rfoot{\thepage}
\renewcommand{\headrulewidth}{0.4pt}
\renewcommand\footrulewidth{0.4pt}
\providecommand{\abs}[1]{\lvert#1\rvert}
\providecommand{\norm}[1]{\lVert#1\rVert}
\providecommand{\dx}{\, \mathrm{d}x}
% \providecommand{\vint}[2]{\int_{#1} \! #2 \, \mathrm{d}x}
% \providecommand{\sint}[2]{\int_{\partial #1} \! #2 \, \mathrm{d}A}
\renewcommand{\div}{\nabla \cdot}
\providecommand{\shape}{\Omega(p)}
\providecommand{\boundary}{\partial \shape}
\providecommand{\vint}[1]{\int_{\shape} \! #1 \, \mathrm{d}x}
\providecommand{\sint}[1]{\int_{\boundary} \! #1 \, \mathrm{d}A}
\providecommand{\pder}[2]{\frac{\partial #1}{\partial #2}}
\providecommand{\tder}[2]{\frac{\mathrm{d} #1}{\mathrm{d} #2}}
\providecommand{\evalat}[2]{\left.#1\right|_{#2}}
\providecommand{\e}{\epsilon}
\newcommand{\defeq}{\vcentcolon=}
\newtheorem{lemma}{Lemma}

\makeatletter
\usepackage{mathtools}
\newcases{mycases}{\quad}{%
  \hfil$\m@th\displaystyle{##}$}{$\m@th\displaystyle{##}$\hfil}{\lbrace}{.}
\makeatother

\date{}
\title{Tensor Flattening}
\author{Julian Panetta}

% BEGIN DOCUMENT
\begin{document}

\maketitle

We discuss how symmetric rank 2 and rank 4 tensors are flattened into vectors
and matrices so that contractions can be implemented efficiently as dot products
and matrix multiplies. We notate this flattening as the overloaded $F$ operator,
which maps symmetric rank 2 tensors to vectors and symmetric rank 4 tensors to
matrices. We use summation notation, where repeated indices imply summation
over the range $0..2$ in 3D and $0..1$ in 2D.

\section{Voigt Notation}
Our approach is to use Voigt notation with zero indexing.
\subsection{Symmetric Rank 2 Tensors}
In this notation, symmetric rank 2 tensors are flattened into vectors in the order:
$$
\text{3D:} \quad
\begin{pmatrix}
    0 & 5 & 4 \\
    \textcolor{lightgray}{5} & 1 & 3 \\
    \textcolor{lightgray}{4} & \textcolor{lightgray}{3} & 2
\end{pmatrix} \quad
\text{2D:} \quad
\begin{pmatrix}
    0 & 2 \\
    \textcolor{lightgray}{2} & 1
\end{pmatrix}.
$$
For example, strain tensors are flattened as:
$$
\text{3D:} \quad
\begin{pmatrix}
    \e_{xx} & \e_{xy} & \e_{xz} \\
    \e_{xy} & \e_{yy} & \e_{yz} \\
    \e_{xz} & \e_{yz} & \e_{zz}
\end{pmatrix} \mapsto
\begin{pmatrix}
    \e_{xx} \\
    \e_{yy} \\
    \e_{zz} \\
    \e_{yz} \\
    \e_{xz} \\
    \e_{xy}
\end{pmatrix}, \quad \quad
\text{2D:} \quad
\begin{pmatrix}
    \e_{xx} & \e_{xy}\\
    \e_{xy} & \e_{yy}\\
\end{pmatrix} \mapsto
\begin{pmatrix}
    \e_{xx} \\
    \e_{yy} \\
    \e_{xy}
\end{pmatrix} \quad
$$
This flattening is implemented by the following formula for flattened index
$\overline{ij}$ in $N$ dimensions:
$$
\overline{ij}(i, j) = \begin{cases}
    i & \text{if } i = j \\
    \frac{N (N + 1)}{2} - \left(i + \frac{j (j - 1)}{2} + 1\right) & i < j \\
    \overline{ij}(j, i)            & i > j
\end{cases} .
$$
We can also go the other direction by solving for the upper triangle $(i, j)$ associated with $\overline{ij}$.
This is done by picking the largest $j$ possible (to ensure we're in the upper
triangle), and then solving for the $i$ index:
$$
j(\overline{ij}) = \begin{cases}
    \overline{ij}  & \text{if } \overline{ij} < N \\
    \left\lfloor \frac{1 + \sqrt{1 + 8 \left(\frac{N (N + 1)}{2} - 1 - \overline{ij} \right)}}{2} \right\rfloor & \text{otherwise}
\end{cases} \quad \quad
i(\overline{ij}) = \begin{cases}
    \overline{ij}  & \text{if } \overline{ij} < N \\
    \frac{N (N + 1)}{2} - 1 - \overline{ij} - \frac{j(\overline{ij}) (j(\overline{ij}) - 1)}{2} & \text{otherwise}
\end{cases}.
$$
\subsection{Symmetric Rank 4 Tensors}
The symmetric rank 2 tensor flattening induces a flattening of symmetric rank 4
tensors that act on symmetric rank 2 tensors by double contraction. We consider
tensors with the symmetries of an elasticity tensor (i.e. major and minor
symmetries):
\begin{equation}
\label{eqn:symmetry}
C_{ijkl} = C_{jikl} = C_{ijlk} = C_{klij}.
\end{equation}
The idea is to flatten this tensor into a matrix so that matrix multiplication
can implement double contraction on both the left and the right: $C : \e =
C_{ijkl} \e_{kl}$ and $\e : C = \e_{ij} C_{ijkl}$. Since $\e_{kl}$ is flattened
into vector entry $\overline{kl}$, we want entries $C_{**kl}$ to end up in
matrix column $\overline{kl}$ so that entries are paired properly for
the left double contraction. Likewise, the right double contraction requires
$C_{ij**}$ to end up in matrix row $\overline{ij}$. Accordingly, we flatten
rank 4 tensors $C_{ijkl}$ into the matrix:
$$ \left[F(C)\right]_{\overline{ij}\,\overline{kl}} = C_{ijkl} \quad \quad \text{where } i = i(\overline{ij}),\ j = j(\overline{ij}),\ k = i(\overline{kl}),\ l = j(\overline{kl})$$
The minor symmetries ($i\leftrightarrow j$ and $k \leftrightarrow l$) of tensor
$C_{ijkl}$ mean no information is lost in this conversion, and
the major symmetry $ij \leftrightarrow kl$ means $F(C)$ is
a symmetric matrix.

\subsection{Examples}
A general rank 4 tensor looks like:
$$
\text{3D:}\quad
C =
\left(
\begin{array}{ccc}
 \left(
\begin{array}{ccc}
 C_{0000} & C_{0001} & C_{0002} \\
 C_{0010} & C_{0011} & C_{0012} \\
 C_{0020} & C_{0021} & C_{0022} \\
\end{array}
\right) & \left(
\begin{array}{ccc}
 C_{0100} & C_{0101} & C_{0102} \\
 C_{0110} & C_{0111} & C_{0112} \\
 C_{0120} & C_{0121} & C_{0122} \\
\end{array}
\right) & \left(
\begin{array}{ccc}
 C_{0200} & C_{0201} & C_{0202} \\
 C_{0210} & C_{0211} & C_{0212} \\
 C_{0220} & C_{0221} & C_{0222} \\
\end{array}
\right) \\
 \left(
\begin{array}{ccc}
 C_{1000} & C_{1001} & C_{1002} \\
 C_{1010} & C_{1011} & C_{1012} \\
 C_{1020} & C_{1021} & C_{1022} \\
\end{array}
\right) & \left(
\begin{array}{ccc}
 C_{1100} & C_{1101} & C_{1102} \\
 C_{1110} & C_{1111} & C_{1112} \\
 C_{1120} & C_{1121} & C_{1122} \\
\end{array}
\right) & \left(
\begin{array}{ccc}
 C_{1200} & C_{1201} & C_{1202} \\
 C_{1210} & C_{1211} & C_{1212} \\
 C_{1220} & C_{1221} & C_{1222} \\
\end{array}
\right) \\
 \left(
\begin{array}{ccc}
 C_{2000} & C_{2001} & C_{2002} \\
 C_{2010} & C_{2011} & C_{2012} \\
 C_{2020} & C_{2021} & C_{2022} \\
\end{array}
\right) & \left(
\begin{array}{ccc}
 C_{2100} & C_{2101} & C_{2102} \\
 C_{2110} & C_{2111} & C_{2112} \\
 C_{2120} & C_{2121} & C_{2122} \\
\end{array}
\right) & \left(
\begin{array}{ccc}
 C_{2200} & C_{2201} & C_{2202} \\
 C_{2210} & C_{2211} & C_{2212} \\
 C_{2220} & C_{2221} & C_{2222} \\
\end{array}
\right) \\
\end{array}
\right)
$$
$$
\text{2D:}\quad
C =
\left(
\begin{array}{cc}
 \left(
\begin{array}{cc}
 C_{0000} & C_{0001} \\
 C_{0010} & C_{0011} \\
\end{array}
\right) & \left(
\begin{array}{cc}
 C_{0100} & C_{0101} \\
 C_{0110} & C_{0111} \\
\end{array}
\right) \\
 \left(
\begin{array}{cc}
 C_{1000} & C_{1001} \\
 C_{1010} & C_{1011} \\
\end{array}
\right) & \left(
\begin{array}{cc}
 C_{1100} & C_{1101} \\
 C_{1110} & C_{1111} \\
\end{array}
\right) \\
\end{array}
\right)
$$
After accounting for the symmetries of an elasticity tensor (\ref{eqn:symmetry}),
these tensors really only have 21 and 6 unique entries in 3D and 2D respectively:
$$
\text{3D:}\quad
C =
\left(\begin{array}{ccc}
 \left(\begin{array}{ccc}
 C_{0000} & C_{0001} & C_{0002} \\
 C_{0001} & C_{0011} & C_{0012} \\
 C_{0002} & C_{0012} & C_{0022} \\ \end{array} \right) &
 \left( \begin{array}{ccc}
 C_{0001} & C_{0101} & C_{0201} \\
 C_{0101} & C_{1101} & C_{1201} \\
 C_{0201} & C_{1201} & C_{2201} \\ \end{array} \right) &
 \left( \begin{array}{ccc}
 C_{0002} & C_{0201} & C_{0202} \\
 C_{0201} & C_{1102} & C_{1202} \\
 C_{0202} & C_{1202} & C_{2202} \\ \end{array} \right) \\
 \left( \begin{array}{ccc}
 C_{0001} & C_{0101} & C_{0201} \\
 C_{0101} & C_{1101} & C_{1201} \\
 C_{0201} & C_{1201} & C_{2201} \\ \end{array} \right) &
 \left( \begin{array}{ccc}
 C_{0011} & C_{1101} & C_{1102} \\
 C_{1101} & C_{1111} & C_{1112} \\
 C_{1102} & C_{1112} & C_{1122} \\ \end{array} \right) &
 \left( \begin{array}{ccc}
 C_{0012} & C_{1201} & C_{1202} \\
 C_{1201} & C_{1112} & C_{1212} \\
 C_{1202} & C_{1212} & C_{2212} \\ \end{array} \right) \\
 \left( \begin{array}{ccc}
 C_{0002} & C_{0201} & C_{0202} \\
 C_{0201} & C_{1102} & C_{1202} \\
 C_{0202} & C_{1202} & C_{2202} \\ \end{array} \right) &
 \left( \begin{array}{ccc}
 C_{0012} & C_{1201} & C_{1202} \\
 C_{1201} & C_{1112} & C_{1212} \\
 C_{1202} & C_{1212} & C_{2212} \\ \end{array} \right) &
 \left( \begin{array}{ccc}
 C_{0022} & C_{2201} & C_{2202} \\
 C_{2201} & C_{1122} & C_{2212} \\
 C_{2202} & C_{2212} & C_{2222} \\ \end{array} \right) \\
\end{array}
\right)
$$
$$
\text{2D:}\quad
C =
\left(
\begin{array}{cc}
 \left(
\begin{array}{cc}
 C_{0000} & C_{0001} \\
 C_{0001} & C_{0011} \\
\end{array}
\right) & \left(
\begin{array}{cc}
 C_{0001} & C_{0101} \\
 C_{0101} & C_{1101} \\
\end{array}
\right) \\
 \left(
\begin{array}{cc}
 C_{0001} & C_{0101} \\
 C_{0101} & C_{1101} \\
\end{array}
\right) & \left(
\begin{array}{cc}
 C_{0011} & C_{1101} \\
 C_{1101} & C_{1111} \\
\end{array}
\right) \\
\end{array}
\right)
$$
And flattening to a symmetric matrix as described gets a single copy of each of
these entries in the upper triangle:
$$
\text{3D:}\quad
F(C) =
\left(
\begin{array}{cccccc}
 C_{0000} & C_{0011} & C_{0022} & C_{0012} & C_{0002} & C_{0001} \\
 C_{0011} & C_{1111} & C_{1122} & C_{1112} & C_{1102} & C_{1101} \\
 C_{0022} & C_{1122} & C_{2222} & C_{2212} & C_{2202} & C_{2201} \\
 C_{0012} & C_{1112} & C_{2212} & C_{1212} & C_{1202} & C_{1201} \\
 C_{0002} & C_{1102} & C_{2202} & C_{1202} & C_{0202} & C_{0201} \\
 C_{0001} & C_{1101} & C_{2201} & C_{1201} & C_{0201} & C_{0101} \\
\end{array}
\right)
$$
$$
\text{2D:}\quad
F(C) =
\left(
\begin{array}{ccc}
 C_{0000} & C_{0011} & C_{0001} \\
 C_{0011} & C_{1111} & C_{1101} \\
 C_{0001} & C_{1101} & C_{0101} \\
\end{array}
\right).
$$

\section{Implementing Double Contraction}
\subsection{The Missing Factor of 2}
We have flattened with the goal of using dot products to implement double contraction
of two rank 2 tensors and matrix-vector multiplication to implement double
contraction of a rank 4 tensor and a rank 2 tensor. However we're not done yet:
the off diagonal entries aren't handled properly! Consider a double contraction
of a stress tensor with a strain tensor in 3D:
$$
\left( \begin{array}{ccc}
 \sigma_{xx} & \sigma_{xy} & \sigma_{xz} \\
 \sigma_{xy} & \sigma_{yy} & \sigma_{yz} \\
 \sigma_{xz} & \sigma_{yz} & \sigma_{zz} \\
\end{array} \right) :
\left( \begin{array}{ccc}
 \epsilon_{xx} & \epsilon_{xy} & \epsilon_{xz} \\
 \epsilon_{xy} & \epsilon_{yy} & \epsilon_{yz} \\
 \epsilon_{xz} & \epsilon_{yz} & \epsilon_{zz} \\
\end{array} \right) =
\sigma_{xx} \epsilon_{xx}+\sigma_{yy} \epsilon_{yy}+\sigma_{zz} \epsilon_{zz}+ 2\left(\sigma_{yz} \epsilon_{yz}+ \sigma_{xz} \epsilon_{xz} + \sigma_{xy} \epsilon_{xy} \right)
$$
Notice the factor of 2 on the off-diagonals, which isn't present in the dot product of the two tensors in flattened form:
$$
\begin{pmatrix}
    \sigma_{xx} &
    \sigma_{yy} &
    \sigma_{zz} &
    \sigma_{yz} &
    \sigma_{xz} &
    \sigma_{xy}
\end{pmatrix}
\begin{pmatrix}
    \e_{xx} \\
    \e_{yy} \\
    \e_{zz} \\
    \e_{yz} \\
    \e_{xz} \\
    \e_{xy}
\end{pmatrix} = \sigma_{xx} \epsilon_{xx}+\sigma_{yy} \epsilon_{yy}+\sigma_{zz} \epsilon_{zz}+ \sigma_{yz} \epsilon_{yz}+ \sigma_{xz} \epsilon_{xz} + \sigma_{xy} \epsilon_{xy}
$$
A similar problem happens with a double contraction of a rank 4 tensor with a
rank 2 tensor. Consider entries of the symmetric stress tensor $\sigma = C :
\epsilon$:
$$
\begin{pmatrix}
\sigma_{xx} \\
\sigma_{yy} \\
\sigma_{zz} \\
\sigma_{yz} \\
\sigma_{xz} \\
\sigma_{xy}
\end{pmatrix} =
\begin{pmatrix}
 C_{0000} \epsilon_{xx}+C_{0011} \epsilon_{yy}+C_{0022} \epsilon_{zz} +2 (C_{0012} \epsilon_{yz} + C_{0002} \epsilon_{xz}+ C_{0001} \epsilon_{xy}) \\
 C_{0011} \epsilon_{xx}+C_{1111} \epsilon_{yy}+C_{1122} \epsilon_{zz} +2 (C_{1112} \epsilon_{yz} + C_{1102} \epsilon_{xz}+ C_{1101} \epsilon_{xy}) \\
 C_{0022} \epsilon_{xx}+C_{1122} \epsilon_{yy}+C_{2222} \epsilon_{zz} +2 (C_{2212} \epsilon_{yz} + C_{2202} \epsilon_{xz}+ C_{2201} \epsilon_{xy}) \\
 C_{0012} \epsilon_{xx}+C_{1112} \epsilon_{yy}+C_{2212} \epsilon_{zz} +2 (C_{1212} \epsilon_{yz} + C_{1202} \epsilon_{xz}+ C_{1201} \epsilon_{xy}) \\
 C_{0002} \epsilon_{xx}+C_{1102} \epsilon_{yy}+C_{2202} \epsilon_{zz} +2 (C_{1202} \epsilon_{yz} + C_{0202} \epsilon_{xz}+ C_{0201} \epsilon_{xy}) \\
 C_{0001} \epsilon_{xx}+C_{1101} \epsilon_{yy}+C_{2201} \epsilon_{zz} +2 (C_{1201} \epsilon_{yz} + C_{0201} \epsilon_{xz}+ C_{0101} \epsilon_{xy})
\end{pmatrix}.
$$
Again, our flattened version of this operation (applying flattened $C$ to the flattened strain vector) gets almost the correct answer, but it misses
the factor of 2 on the off-diagonal strain contributions:
$$
F(C) F(\epsilon) =
F(C)
\begin{pmatrix}
\epsilon_{xx} \\
\epsilon_{yy} \\
\epsilon_{zz} \\
\epsilon_{yz} \\
\epsilon_{xz} \\
\epsilon_{xy}
\end{pmatrix} =
\begin{pmatrix}
C_{0000} \epsilon_{xx} + C_{0011} \epsilon_{yy} + C_{0022} \epsilon_{zz} + C_{0012} \epsilon_{yz} + C_{0002} \epsilon_{xz} + C_{0001} \epsilon_{xy} \\
C_{0011} \epsilon_{xx} + C_{1111} \epsilon_{yy} + C_{1122} \epsilon_{zz} + C_{1112} \epsilon_{yz} + C_{1102} \epsilon_{xz} + C_{1101} \epsilon_{xy} \\
C_{0022} \epsilon_{xx} + C_{1122} \epsilon_{yy} + C_{2222} \epsilon_{zz} + C_{2212} \epsilon_{yz} + C_{2202} \epsilon_{xz} + C_{2201} \epsilon_{xy} \\
C_{0012} \epsilon_{xx} + C_{1112} \epsilon_{yy} + C_{2212} \epsilon_{zz} + C_{1212} \epsilon_{yz} + C_{1202} \epsilon_{xz} + C_{1201} \epsilon_{xy} \\
C_{0002} \epsilon_{xx} + C_{1102} \epsilon_{yy} + C_{2202} \epsilon_{zz} + C_{1202} \epsilon_{yz} + C_{0202} \epsilon_{xz} + C_{0201} \epsilon_{xy} \\
C_{0001} \epsilon_{xx} + C_{1101} \epsilon_{yy} + C_{2201} \epsilon_{zz} + C_{1201} \epsilon_{yz} + C_{0201} \epsilon_{xz} + C_{0101} \epsilon_{xy}
\end{pmatrix}.
$$
Finally, by symmetry, an identical problem happens when implementing the double
contraction $\e_{ij} C_{ijkl}$ as $F(\e)^T F(C)$.

\subsection{Solving the Problem}
There are several ways to introduce the factors of two. We can double the right
half of the $F(C)$ matrix, but this breaks its symmetry. We can leave $F(C)$
unchanged, but work with ``engineering strain'' $(\e_{xx}, \e_{yy}, \e_{zz}, 2
\e_{yz}, 2 \e_{xz}, 2 \e_{xy})^T$, but this means strain and stress are
flattened differently, which could lead to mistakes. We could weight the off
diagonal terms of all symmetric rank 2 tensors by $\sqrt{2}$ (this is called
Kelvin or Mandel notation) but this also requires modifying the $F(C)$
matrix--though this time in a symmetric way.

Instead of any of these, we choose to work with true, unmodified strain/stress
quantities and the original, symmetric $F(C)$, but to include a ``shear
doubling'' matrix that doubles the off-diagonal entries each time a double
contraction is taken. This means that off diagonals of strain and stress are
flattened the same way, $F(C)$ remains symmetric, and every entry in $F(C)$ is
the exact coefficient(s) that flattened to it rather than a scaled version. This
shear doubling matrix is given by
$$
\text{3D: }\quad \mathscr{D} = \begin{pmatrix}
        1 & 0 & 0 & 0 & 0 & 0 \\
        0 & 1 & 0 & 0 & 0 & 0 \\
        0 & 0 & 1 & 0 & 0 & 0 \\
        0 & 0 & 0 & 2 & 0 & 0 \\
        0 & 0 & 0 & 0 & 2 & 0 \\
        0 & 0 & 0 & 0 & 0 & 2 \\
    \end{pmatrix}, \quad \quad
    \text{2D: }\quad \mathscr{D} = \begin{pmatrix}
        1 & 0 & 0 \\
        0 & 1 & 0 \\
        0 & 0 & 2 \\
    \end{pmatrix}
$$
\subsection{Examples}
Finally, we show examples of double contraction implemented by matrix-vector
multiplies and inner products in our notation:
\begin{gather*}
    F(\sigma) = F(C : \e) = F(C_{ijkl} \e_{kl}) = F(C) \mathscr{D} F(\e), \\
    \sigma : \e = \sigma_{ij} \e_{kl} = F(\sigma)^T \mathscr{D} F(\e), \\
    \e : C : \e = \e_{ij} C_{ijkl} \e_{kl} = {\bf e}^T \mathscr{D} F(C) \mathscr{D} {\bf e}, \\
    F(A : B) = F(A_{ijpq} B_{pqkl}) = F(A) \mathscr{D} F(B)
\end{gather*}

Note, however, that the last operation does not result in a tensor with major
symmetries unless $A_{ijpq} = B_{pqij}$. This means that the matrix representing
the flattened result is not necessarily symmetric.

\section{Quadruple Contraction}
We can also implement quadruple contraction in flattened form:
$$
A_{ijkl} B_{ijkl}
$$
This is useful in several settings, including computing the Frobenius norm of a
tensor. Each term of the contraction will be an entry of F(A) multiplied by the
corresponding entry of F(B)---we just need to determine how many times each
flattened entry appears. That is, we can compute the contraction by instead
summing over entries of the flattened tensors, weighting each term by how many
components are flattened down to that particular entry. The weights can be
determined from the minor symmetries alone; major symmetry just causes a
symmetric flattened tensor as opposed to flattening more components of the
tensor to a single flattened entry.

Below are the counts of how many tensor components are represented by each
flattened entry:
$$
\text{3D: }\begin{pmatrix}
        1 & 1 & 1 & 2 & 2 & 2 \\
        1 & 1 & 1 & 2 & 2 & 2 \\
        1 & 1 & 1 & 2 & 2 & 2 \\
        2 & 2 & 2 & 4 & 4 & 4 \\
        2 & 2 & 2 & 4 & 4 & 4 \\
        2 & 2 & 2 & 4 & 4 & 4 \\
    \end{pmatrix}, \quad \quad
\text{2D: }\begin{pmatrix}
        1 & 1 & 2 \\
        1 & 1 & 2 \\
        2 & 2 & 4 \\
    \end{pmatrix}
$$
This per-entry weighting can be applied to a flattened tensor by ``shear
doubling'' both the rows and columns,
$$
\mathscr{D} F(A) \mathscr{D},
$$
allowing us to implement the quadruple contraction as a double contraction of
the scaled flattened tensors:
$$
A_{ijkl} B_{ijkl} = [\mathscr{D} F(A) \mathscr{D}] : F(B) = [F(A)\mathscr{D}] : [\mathscr{D} F(B)] = [\mathscr{D}F(A)] : [F(B)\mathscr{D}] =
\mathscr{D}^{\frac{1}{2}} F(A) \mathscr{D}^{\frac{1}{2}} : \mathscr{D}^{\frac{1}{2}} F(B) \mathscr{D}^{\frac{1}{2}} :
$$
In linear algebra terms, this is:
$$
A_{ijkl} B_{ijkl} = \tr\left(\mathscr{D} F(A)^T \mathscr{D} F(B)\right) = \tr\left(F(A)^T \mathscr{D} F(B) \mathscr{D}\right)
= \tr\left(\mathscr{D}^{\frac{1}{2}} F(A)^T \mathscr{D}^{\frac{1}{2}} \mathscr{D}^{\frac{1}{2}} F(B) \mathscr{D}^{\frac{1}{2}} \right)
$$
\subsection{Examples}
As an example, we compute the squared Frobenius norm of an orthotropic
elasticity (or compliance) tensor:
$$
\text{3D: } \quad F(C) = \begin{pmatrix}
    C_{0000} & C_{0011} & C_{0022} & 0 & 0 & 0 \\
    C_{0011} & C_{1111} & C_{1122} & 0 & 0 & 0 \\
    C_{0022} & C_{1122} & C_{2222} & 0 & 0 & 0 \\
           0 &        0 &        0 & C_{1212} & 0 & 0 \\
           0 &        0 &        0 & 0 & C_{0202} & 0 \\
           0 &        0 &        0 & 0 & 0 & C_{0101} \\
\end{pmatrix}, \quad \quad
\text{2D: } \quad F(C) = \begin{pmatrix}
    C_{0000} & C_{0011} & 0 \\
    C_{0011} & C_{1111} & 0 \\
           0 &        0 & C_{0101} \\
\end{pmatrix}
$$
In 3D the Frobenius norm is
\begin{align*}
    \norm{C}^2_F = &C_{ijkl} : C_{ijkl} = \norm{\mathscr{D}^{1/2} F(C) \mathscr{D}^{1/2}}^2_F = \\
&C_{0000}^2 + C_{1111}^2 + C_{2222}^2 + 2 C_{0011}^2 + 2 C_{0022}^2 + 2 C_{1122}^2
+ 4 C_{1212}^2 + 4 C_{0202}^2 + 4 C_{0101}^2,
\end{align*}
and in 2D:
\begin{align*}
    \norm{C}^2_F = C_{ijkl} : C_{ijkl} = \norm{\mathscr{D}^{1/2} F(C) \mathscr{D}^{1/2}}^2_F = C_{0000}^2 + C_{1111}^2 + 2 C_{0011}^2 + 4 C_{0101}^2.
\end{align*}
Notice that the only difference between $\norm{C}_F^2$ and $\norm{F(C)}_F^2$
for orthotropic tensors is the weight of 4 on the ``shear'' terms.

\section{Rank 4 Identities and Inverses}
Defining the rank 4 identity and inverse operators in a way that is consistent
with our flattening conventions requires a bit of care. While linear mappings
on symmetric rank 2 tensors, $Sym^{n\times n} \to Sym^{n\times n}$, do not have
a unique representations as a rank 4 tensors in a particular basis, they do
have unique representations with the symmetries required for flattening. We
provide this unique definition below.

\subsection{Symmetric Rank 4 Identity}
We flatten the identity tensor defined by its action  on $X \in Sym^{n \times
n}$:

\begin{equation}
\label{eqn:identityDef}
I_{ijkl} X_{kl} =  X_{ij}.
\end{equation}

Obviously the tensor $\delta_{ik} \delta_{jl}$ implements this map, but it
doesn't have the symmetries of an elasticity tensor and cannot be flattened.
However, since (\ref{eqn:identityDef}) does not
uniquely define I, we are free to choose the unique rank 4 tensor that has major and
minor symmetries:
$I_{ijkl} = \frac{1}{2}\left(\delta_{ik} \delta_{jl} + \delta_{il} \delta_{jk} \right)$.
This choice can also be derived/motivated as follows.

We wish to find the symmetric tensor $I$ such that for all $X \in Sym^{nxn}$:
\begin{gather*}
F(I : X) = F(X) \quad \Longrightarrow \quad
F(I) \mathscr{D} F(X) = F(X) \quad \Longrightarrow \quad
\boxed{
F(I) = \mathscr{D}^{-1},}
\end{gather*}
which is indeed what we get when flattening the symmetrized identity,
$I_{ijkl} = \frac{1}{2}\left(\delta_{ik} \delta_{jl} + \delta_{il} \delta_{jk}
\right)$. As a sanity check, we can verify that our identity works for double
contraction of rank 4 tensors as well:
$$
F(I : A) = F(I) \mathscr{D} F(A) = \mathscr{D}^{-1} \mathscr{D} F(A) = F(A). \quad \checkmark
$$

\subsection{Symmetric Rank 4 Inverse}
Now we define the inverse, $A^{-1}$, of a symmetric rank 4 tensor $A$ by
\begin{equation}
\label{eqn:identityDef}
A_{ijpq} A^{-1}_{pqkl} = I_{ijkl},
\end{equation}
but again, due to symmetries, the definition is not unique. We derive the
unique inverse that has major and minor symmetries by finding its flattened
form:
$$
F(A:A^{-1}) = F(I) \quad \Longrightarrow \quad  F(A) \mathscr{D} F(A^{-1}) = \mathscr{D}^{-1} \quad
\Longrightarrow \quad \boxed{F(A^{-1}) = \mathscr{D}^{-1} F(A)^{-1} \mathscr{D}^{-1},}
$$
and recovering $A^{-1}$ itself by unflattening.

\subsection{Examples}
\subsubsection{Inverse of the Identity}
We do another simple sanity check of our notation, that the identity is its own
inverse:
$$
F(I^{-1}) = \mathscr{D}^{-1} F(I)^{-1} \mathscr{D}^{-1} = \mathscr{D}^{-1} \mathscr{D} \mathscr{D}^{-1} = \mathscr{D}^{-1} = F(I) \quad \checkmark
$$

\subsubsection{Orthotropic Material}
An orthotropic material parametrized by Young's moduli $E$, Poisson ratios $\nu$
and shear moduli $\mu$ has a compliance tensor, $C^{-1}$, with a particularly simple
flattened form. The mapping from stress to strain tensors can be expressed as:
$$
\text{3D: } F(\epsilon) =
\begin{pmatrix}
    \frac{1}{E_x} & -\frac{\nu_{yx}}{E_y} & -\frac{\nu_{zx}}{E_z} & 0 & 0 & 0 \\
    -\frac{\nu_{yx}}{E_y} & \frac{1}{E_y} & -\frac{\nu_{zy}}{E_z} & 0 & 0 & 0 \\
    -\frac{\nu_{zx}}{E_z} & -\frac{\nu_{zy}}{E_z} & \frac{1}{E_z} & 0 & 0 & 0 \\
    0 & 0 & 0 & \frac{1}{2 \mu_{yz}} & 0 & 0 \\
    0 & 0 & 0 & 0 & \frac{1}{2 \mu_{zx}} & 0 \\
    0 & 0 & 0 & 0 & 0 & \frac{1}{2 \mu_{xy}}
\end{pmatrix} F(\sigma), \quad
\text{2D: } F(\epsilon) =
\begin{pmatrix}
    \frac{1}{E_x} & -\frac{\nu_{yx}}{E_y} & 0 \\
    -\frac{\nu_{yx}}{E_y} & \frac{1}{E_y}  & 0 \\
    0 & 0 & \frac{1}{2 \mu_{xy}}
\end{pmatrix} F(\sigma)
$$
Since our flattened double contraction formula inserts a shear doubler $\mathscr{D}$ between its
arguments, we need to apply $\mathscr{D}^{-1}$ to the matrix above to arrive at $F(C^{-1})$
(so that the double contraction $C^{-1} : \sigma$ is this mapping):
$$
\text{3D: } F(C^{-1}) =
\begin{pmatrix}
    \frac{1}{E_x} & -\frac{\nu_{yx}}{E_y} & -\frac{\nu_{zx}}{E_z} & 0 & 0 & 0 \\
    -\frac{\nu_{yx}}{E_y} & \frac{1}{E_y} & -\frac{\nu_{zy}}{E_z} & 0 & 0 & 0 \\
    -\frac{\nu_{zx}}{E_z} & -\frac{\nu_{zy}}{E_z} & \frac{1}{E_z} & 0 & 0 & 0 \\
    0 & 0 & 0 & \frac{1}{4 \mu_{yz}} & 0 & 0 \\
    0 & 0 & 0 & 0 & \frac{1}{4 \mu_{zx}} & 0 \\
    0 & 0 & 0 & 0 & 0 & \frac{1}{4 \mu_{xy}}
\end{pmatrix}, \quad
\text{2D: } F(C^{-1}) =
\begin{pmatrix}
    \frac{1}{E_x} & -\frac{\nu_{yx}}{E_y} & 0 \\
    -\frac{\nu_{yx}}{E_y} & \frac{1}{E_y}  & 0 \\
    0 & 0 & \frac{1}{4 \mu}
\end{pmatrix}.
$$
Then we can apply our inverse formula,
$
F(C) = \mathscr{D}^{-1} F\left(C^{-1}\right)^{-1} \mathscr{D}^{-1}$ to get:
\begin{gather*}
\text{3D: } F(C) =
\begin{pmatrix}
 \frac{E_{x} E_{y} \left(E_{y} v_{zy}^2-E_{z}\right)}{\text{denominator}} & -\frac{E_{x} E_{y} (E_{z} v_{yx}+E_{y} v_{zx} v_{zy})}{\text{denominator}} & -\frac{E_{x} E_{y} E_{z} (v_{zx}+v_{yx} v_{zy})}{\text{denominator}} & 0 & 0 & 0 \\
 -\frac{E_{x} E_{y} (E_{z} v_{yx}+E_{y} v_{zx} v_{zy})}{\text{denominator}} & \frac{E_{y}^2 \left(E_{x} v_{zx}^2-E_{z}\right)}{\text{denominator}} & -\frac{E_{y} E_{z} (E_{x} v_{yx} v_{zx}+E_{y} v_{zy})}{\text{denominator}} & 0 & 0 & 0 \\
 -\frac{E_{x} E_{y} E_{z} (v_{zx}+v_{yx} v_{zy})}{\text{denominator}} & -\frac{E_{y} E_{z} (E_{x} v_{yx} v_{zx}+E_{y} v_{zy})}{\text{denominator}} & \frac{E_{z}^2 \left(E_{x} v_{yx}^2-E_{y}\right)}{\text{denominator}} & 0 & 0 & 0 \\
 0 & 0 & 0 & \mu_{yz} & 0 & 0 \\
 0 & 0 & 0 & 0 & \mu_{zx} & 0 \\
 0 & 0 & 0 & 0 & 0 & \mu_{xy} \\
\end{pmatrix}, \\
\text{2D: } F(C) =
\begin{pmatrix}
    \frac{E_{x} E_{y}}{E_{y}-E_{x} \nu_{yx}^2} &
    \frac{E_{x} E_{y} \nu_{yx}}{E_{y}-E_{x} \nu_{yx}^2}
    & 0 \\
     \frac{E_{x} E_{y} \nu_{yx}}{E_{y}-E_{x} \nu_{yx}^2}
     & \frac{E_{y}^2}{E_{y}-E_{x} \nu_{yx}^2} & 0 \\
      0 & 0 & \mu \\
 \end{pmatrix}
\end{gather*}
with 3D denominator $E_x E_z \nu_{yx}^2 + E_y^2 \nu_{zy}^2 + E_y (-E_z + E_x
\nu_{zx} (\nu_{zx} + 2 \nu_{yx} \nu_{zy}))$.

\end{document}


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Implementation
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\section{Implementation}

% IMPLEMENTATION
\subsection{Sequential}
We attempted to make the sequential implementation as fast as possible to give a valid comparison to the OpenCL version; this would show if the OpenCL implementation was worth the effort.

\subsubsection{Optimisations}
Our implementation uses an online algorithm to calculate the core values on every data point, this retains numerical accuracy and is the same speed as the `chunked' version (even correctly tuned for the cache). Partial calculations are reused to determine more than one core statistical value to reduce operations. The current value of the statistical values is only updated in chunks to reduce overhead.

\subsubsection{Implementation}
\begin{listing}[H]
\inputminted{python}{sequential.py}
\caption{The equations mentioned above were implemented in the incremental case}
\label{sequential:3}
\end{listing}

\subsection{Parallel}
\subsubsection{Kernel}
We reduced all our statistical calculations down to one kernel. One kernel allows for a lesser amount of overhead when transferring data to and from the host, which would increase the overall efficiency of our implementation. Minimum, maximum and all the moments are calculated in a singular kernel with a reduction based for loop with different strides for efficient and concurrent calculations. Barriers are used in assistance to strides to allow the kernel to execute without issues with concurrency. This kernel operates using the data parallelism approach as each data point is operated on by a different compute unit. The kernel is only compiled once but can be run multiple times in `chunks' when the data size exceeds the supported size of the device.

\subsubsection{Optimisations}
When we were finished implementing the base statistics in the \texttt{stats.cl} kernel, we looked at optimising the calculations to reduce the number of operations, and to reduce the total memory used. Reducing space was vital as it allowed for more memory to be used on other statistical value storage, and reducing calculations decreased the time taken to calculate each statistical value, hence increasing speed up overall.

\paragraph{Data Transfer and Memory}
In order to increase the throughput of data transfer, we had to reduce both allocated memory on devices, and data transferred into buffers. These two are intertwined as reducing one, allows the other to increase. Being able to increase the rate of data transfer is important as it provides an increase in the work group size and amount of work groups that could be transferred. Therefore by implementing this optimisation technique, larger vectors could be transferred onto the input buffer, so that more data could be processed in parallel, with less transfer time overhead.\\

An example of when we executed this technique was min/max reduction and calculation, as can be seen in listing \ref{parallel:1}. Initially, we had both minimum and maximum being calculated in separate arrays, being reduced down to one work item per work group at the end. This process meant that one array was needed for both minimum and maximum, and required more memory to hold. What we did to solve this was to reduce and calculate on the first iteration, and max/min would naturally separate the maximum and minimum values. Once this was done, maximum and minimum calculations could be done on separate sections of the array, without having to worry about concurrent access.\\

\begin{listing}[ht]
\inputminted{c++}{maxmin.cpp}
\vspace{-16pt}
\caption{Work item calculation splitting over min and max}
\label{parallel:1}
\end{listing}

The moments structure is an array of structures, but its length only requires half the amount of memory for the whole array, compared with the input array. It is able to be constructed like this as the initial moment calculation is done on the original array (min/max array), and reduced down to half of the total work group size instantly, onto this structure.\\

\paragraph{Calculations}
As stated above, we reduced the number of calculations by doing the first minimum and maximum calculation together. Another way we reduced calculations was to minimise the amount divisions used, as divisions can be rather expensive. When reducing down, we also made sure that as many work items would still be doing work, this would help increase occupancy and decrease downtime of work-items hence increasing overall speed.

\subsection{File Reading}
File reading was a common functionality that was needed between the parallel and sequential implementation. It was essential that both sequential and parallel used the same file reading process as it would help isolate the speedup between calculations, and not reading files. When dealing with large files, it is inefficient to read a file fully into memory and needs to be buffered in some way. Originally we had files being read in using the typical \texttt{std::stringstream} and \texttt{getline()}, but this proved to be inefficient and slow. Now the file is loaded into an internal stream buffer; the buffer is traversed to find the number of characters in it. Then a char pointer is allocated the size calculated from before and \texttt{buffer->sgetn(pointer, size)} is called to copy the buffer into the pointer.

\subsection{Command line arguments}
We have added some command line argument options for the user to change the run time behaviour to pick what is best for them. The sequential and OpenCL implementation can be used, output file writing, input file to read etc.\\

For the OpenCL implementation the user can specify different vector input sizes which corresponds to a work group size. Usually, with GPUs, a larger work group size will give better performance but too large could either crash the device (bigger than it can fit) or cause incorrect results to be given (using zeros because it's going out of bounds). Furthermore, selecting a vector size that is not an exponential of 2 crashes some devices.


\section{Properties of the classifying
space}\label{classifying-space-properties}
%A great reference for simplicial sets is Goerss-Jardine, but it's about 500 pages long.
%Another reference is Weibel's homological algebra.
%And also May's simplicial objects book.

%Remember that $\Deltab\ni[n]$ for $n\geq 0$ where $[n] = \{0,\cdots,n\}$.
%A simplicial object is a functor $X:\Deltab^{op}\to\cc$.
%Remember that:
%$$
%|X| = \coprod_{n\geq 0}\Delta^n\times X_n/\sim
%$$
%Remember that $s^i:[n]\to [n+1]$ repeats $i$. Part of this relation $\sim$ is that $(v,s_i x)\sim (s^i v,x)$.
%That means that $x\in X_{n-1}$ and $v\in\Deltab^n$.
%This tells us that $\img(s_i:X_{n-1}\to X_{n})$ indexes cells that lie in the $(n-1)$-skeleton $\mathrm{sk}_{n-1}|X| = \coprod_{k\leq n-1}\Delta^k\times X_k/\sim$.
%
%This gives two functors
%$$
%|-|:s\Set\stackrel{\rightarrow}{\leftarrow} \Top:\Sin
%$$
%and I defined the unit and counit last time.
One important result in the story of geometric realization introduced in the
last section is the following theorem of Milnor's.
\begin{theorem}[Milnor]
    Let $X$ be a space. The map $|\Sin(X)|\to X$ is a weak equivalence.
\end{theorem}
As a consequence, this begets a functorial CW-approximation to $X$.
Unforunately, it's rather large.

In the last section, we saw that $|-|$ was a left adjoint. Therefore, it
preserves colimits (Theorem \ref{adjointslimits}). Surprisingly, it also
preserves products:
\begin{exercise}[Hard]\label{realization-product}
    Let $X$ and $Y$ be simplicial sets. Their product $X\times Y$ is defined to
    be the simplicial set such that $(X\times Y)_n = X_n\times Y_n$. Under this
    notion of product, there is a homeomorphism
    $$|X\times Y|\xrightarrow{\simeq} |X|\times |Y|.$$
    It is important that this product is taken in the category of $k$-spaces.
\end{exercise}

Last time, we defined the classifying space $B\cc$ of $\cc$ to be $|N\cc|$.
\begin{theorem}\label{classifying-product}
    The natural map $B(\cc\times\cd)\xrightarrow{\simeq}B\cc\times B\cd$ is a
    homeomorphism\footnote{Recall that if $\cc$ and $\cd$ are categories, the
    product $\cc\times\cd$ is the category whose objects are pairs of objects
    of $\cc$ and $\cd$, and whose morphisms are pairs of morphisms in $\cc$ and
    $\cd$.}.
\end{theorem}
\begin{proof}
    It is clear that $N(\cc\times\cd) \simeq N\cc\times N\cd$. Since $B\cc =
    |N\cc|$, the desired result follows from Exercise
    \ref{realization-product}.
\end{proof}
In light of Theorem \ref{classifying-product}, it is natural to ask how natural
transformations behave under the classifying space functor. To discuss this, we
need some categorical preliminaries.

The category $\Cat$ is Cartesian closed (Definition \ref{cartesian-closed}).
Indeed, the right adjoint to the product is given by the functor
$\cd\mapsto\Fun(\cc,\cd)$, as can be directly verified.

Consider the category $[1]$. This is particularly simple: a functor $[1]\to\cc$
is just an arrow in $\cc$. It follows that a functor $[1]\to\cd^\cc$ is a
natural transformation between two functors $f_0$ and $f_1$ from $\cc$ to
$\cd$. By our discussion above, this is the same as a functor
$\cc\times[1]\to\cd$.

By Theorem \ref{classifying-product}, we have a homeomorphism $B([1]\times
\cc)\simeq B[1]\times B\cc$. One can show that $B[1] = \Delta^1$, so a natural
transformation between $f_0$ and $f_1$ begets a map $\Delta^1\times B\cc\to
B\cd$ between $Bf_0$ and $Bf_1$. Concretely:
\begin{lemma}\label{nat-trans-htpy}
    A natural transformation $\theta:f_0\to f_1$ where $f_0,f_1:\cc\to\cd$
    induces a homotopy $Bf_0\sim Bf_1:B\cc\to B\cd$.
\end{lemma}
An interesting comment is in order. The notion of a homotopy is ``reversible'',
but that is definitely not true for natural transformations! The functor $B$
therefore ``forgets the polarity in $\Cat$''.

Lemma \ref{nat-trans-htpy} is quite powerful: consider an adjunction $L\dashv
R$ where $L:\cc\to \cd$; then we have natural transformations given by the unit
$1_\cc\to RL$ and the counit $LR\to 1_\cd$. By Lemma \ref{nat-trans-htpy} we
get a homotopy equivalence between $B\cc$ and $B\cd$. In other words, two
categories that are related by any adjoint pair are homotopy equivalent.

A special case of the above discussion yields a rather surprising result.
Consider the category $[0]$. Let $\cd$ be another category such that there is
an adjoint pair $L\dashv R$ where $L:[0]\to \cd$. Then $L$ determines an object
$\ast$ of $\cd$. Let $d$ be any object of $\cd$. We have the counit $LR(d)\to
d$; but $LR(d) = \ast$, so there is a unique morphism $\ast\to X$. (To see
uniqueness, note that the adjunction $L\dashv R$ gives an identification
$\cd(\ast,X) = \cc(0,0) = 0$.) In other words, such a category $\cd$ is simply
a category with an initial object.

Arguing similarly, any category $\cd$ with adjunction $L\dashv R$ where
$L:\cd\to [0]$ is simply a category with a terminal object. From our discussion
above, we conclude that if $\cd$ is any category with a terminal (or initial)
object, then $B\cd$ is contractible.