\documentclass{aiaa-tc}

\usepackage{color}
\usepackage{amsmath}
%\usepackage{overcite}
\usepackage{graphicx}
\usepackage{subfig}
\usepackage{authblk}
\usepackage{amsfonts}

\input basic.ltx
\def\directory{EPSF/}

%------------------------------------------------------------------------------
% MAS Additions
\newcommand{\mas}[1]{\textcolor{magenta}{#1}}
\newcommand{\qw}[1]{\textcolor{blue}{#1}}
%useful for showing deleted text
\renewcommand{\kill}[1]{\textcolor{red}{\sout{#1}}}

\usepackage[normalem]{ulem}  % mas addition
\newcommand{\uvec}[1]{\bar{#1}}
\newcommand{\tens}[1]{\underline{\underline{#1}}}
\renewcommand{\vec}[1]{\underline{#1}}
\renewcommand{\skew}[1]{\widetilde{#1}}
%------------------------------------------------------------------------------

\title{Partitioned Nonlinear Structural Analysis\\
of Wind Turbines Using BeamDyn\thanks{The submitted manuscript has been offered by employees
of the Alliance for Sustainable Energy, LLC (Alliance), a contractor of the U.S.\
Government under Contract No.\ DE-AC36-08GO28308.  Accordingly, the U.S.\
Government and Alliance retain a nonexclusive royalty-free license to publish or
reproduce the published form of this contribution, or allow others to do so, for
U.S.\ Government purposes.} }

\author[]{Qi Wang\thanks{Research Engineer, National Wind Technology Center, AIAA Professional Member. Email: Qi.Wang2@nrel.gov}}
 \author[]{Michael A. Sprague\thanks{Senior Research Scientist,
Computational Science Center, AIAA Professional Member.}}
 \author[]{Jason Jonkman\thanks{Senior Engineer, National Wind Technology Center, AIAA Professional Member.}}
  \author[]{Bonnie Jonkman\thanks{Senior Scientist, National Wind Technology Center, AIAA Professional Member.}}
 \affil[]{National Renewable Energy Laboratory, Golden, CO 80401}

 \renewcommand\Authands{, and }

\begin{document}

\maketitle

\begin{abstract}
{We review BeamDyn as a module coupled under the FAST modularization framework, which provides loose coupling between multi-physics modules in time-domain simulations.
BeamDyn, which is a beam solver based on Legendre spectral finite elements and geometrically exact beam theory, was created for high-fidelity predictive simulations of modern, highly flexible wind turbine blades.
After reviewing the underlying BeamDyn theory and implementation, we describe the FAST coupling algorithm, which was designed for wind turbine analysis.
Numerical experiments with a simple beam-spring-damper-mass system are used to verify and demonstrate some of the features of BeamDyn and the loose-coupling algorithm.
Realistic wind turbine simulations are performed with BeamDyn for the NREL 5-MW reference turbine, and we compare results against the established FAST-ElastoDyn blade model.
We demonstrate that the 5-MW blade can be accurately simulated with only six nodes in the BeamDyn blade model when evaluated with an accurate quadrature scheme that captures all provided sectional properties.}
\end{abstract}

\section{Introduction}
Recently, researchers at the National Renewable Energy Laboratory (NREL)
developed BeamDyn\cite{Wang:SFE2013,Wang:GEBT2014,Wang-etal:2015,website:beamdyn}, an open-source nonlinear structural dynamics module for composite wind turbine blade analysis, in the FAST modularization framework.
BeamDyn, which is founded on geometrically exact beam theory (GEBT), was created to accurately simulate the large, nonlinear deflections of modern wind turbine blades that are designed with aero-elastic tailoring and complicated composite structures.
GEBT was first proposed by Reissner\cite{Ressiner1973} and then extended to three-dimensional (3D) beams by Simo\cite{Simo1985} and Simo and Vu-Quoc\cite{Simo1986}.
Readers are referred to
Hodges,\cite{HodgesBeamBook} in which comprehensive derivations and discussions
on nonlinear composite-beam theories can be found.
In the BeamDyn implementation, the GEBT equations are discrretized spatially with Legendre spectral finite elements (LSFEs), which are $p$-type high-order elements that combine the accuracy of global spectral methods with the geometric modeling flexibility of low-order $h$-type elements.
More details on BeamDyn can be found in Wang et al.\cite{Wang:GEBT2014}

FAST is NREL's flagship multi-physics engineering tool for simulating both land-based and offshore wind turbines under realistic operating conditions.
The established FAST blade model included in the module ElastoDyn is incapable
of predictive analysis of highly flexible, composite wind turbine blades.
FAST has been reformulated under a new modularized framework that provides a rigorous means by which various mathematical systems are implemented in distinct modules and coupled to other modules.
The restructuring of FAST greatly enhanced flexibility and expandability to enable further developments of functionality without the need to recode established modules.
These modules are interconnected to solve for the coupled dynamic response of wind turbines. \cite{Jonkman:2013,website:FASTModularizationFramework} In previous work, this framework was extended to handle the non-matching spatial grids at interfaces and non-matching temporal meshes that allow module solutions to advance with different time increments and different time integrators.\cite{Sprague:FAST2015}

In this paper, the formulation of the beam theory is first reviewed and then the coupling algorithm of BeamDyn with other modules is presented.
%The numerical integration scheme that specifically implemented for wind turbine analysis is also introduced.
Numerical examples are provided to demonstrate the capabilities of BeamDyn and its coupling algorithm when it is running in a coupled-to-FAST mode.

\section{Formulation and Implementation}

\subsection{Geometrically Exact Beam Theory}

This section briefly reviews the geometrically exact beam theory. Further details on the content of this section can be found in many other papers\cite{YuGEBT} and textbooks. \cite{HodgesBeamBook,Bauchau:2010}
Figure~\ref{Kinematics} shows a beam in its initial undeformed
and deformed states. A reference frame $\mathbf{b}_i$ is introduced along the
beam axis for the undeformed state and a frame $\mathbf{B}_i$ is introduced
along each point of the deformed beam axis. The curvilinear coordinate $x_1$ defines the intrinsic parameterization of the reference line.

\begin{figure}[h!tp]
\centering
\includegraphics[clip,width=0.7\textwidth]{\directory Kinematics.pdf}
\caption{A beam deformation schematic.} \label{Kinematics}
\end{figure}

In this paper, matrix notation is used to denote vectorial or vectorial-like quantities. For example, an underline denotes a vector $\underline{u}$, a bar denotes a unit vector $\bar{n}$, and a double underline denotes a tensor $\underline{\underline{\Delta}}$. Note that sometimes the underlines only denote the dimension of the corresponding matrix. The governing equations of motion for geometrically exact beam theory (without structural damping) can be written as \cite{Bauchau:2010}
\begin{align}
	\label{GovernGEBT-1}
	\dot{\underline{h}} - \underline{F}^\prime &= \underline{f} \\
	\label{GovernGEBT-2}
	\dot{\underline{g}} + \dot{\tilde{u}} \underline{h} - \underline{M}^\prime + (\tilde{x}_0^\prime + \tilde{u}^\prime)^T \underline{F} &= \underline{m}
\end{align}
where $\vec{h}$ and $\vec{g}$ are the linear and angular momenta resolved in the inertial coordinate system, respectively, $\vec{F}$ and $\vec{M}$ are the beam's sectional force and moment resultants, respectively, $\vec{u}$ is the one-dimensional (1D) displacement of a point on the reference line, $\vec{x}_0$ is the position vector of a point along the beam's reference line,  and $\vec{f}$ and $\vec{m}$ are the distributed force and moment, respectively, applied to the beam structure.  The notation $(\cdot)^\prime$ indicates a derivative with respect to beam axis $x_1$ and $\dot{(\cdot)}$ indicates a derivative with respect to time. The tilde operator $(\skew{\cdot})$ defines a skew-symmetric tensor corresponding to the given vector. In the literature, it is also termed as ``cross-product matrix." For example, given $\vec{n}^T=[ n_1\quad n_2 \quad n_3]$,
\begin{align}
	\skew{n} = \begin{bmatrix}
			0 & -n_3 & n_2 \\
			n_3 & 0 & -n_1 \\
			-n_2 & n_1 & 0\\
			\end{bmatrix}
\end{align}
The constitutive equations relate the velocities to the momenta and the 1D strain measures to the sectional resultants as
\begin{align}
	\label{ConstitutiveMass}
	\begin{Bmatrix}
	\underline{h} \\
	\underline{g}
	\end{Bmatrix}
	= \underline{\underline{\mathcal{M}}} \begin{Bmatrix}
	\dot{\underline{u}} \\
	\underline{\omega}
	\end{Bmatrix} \\
	\label{ConstitutiveStiff}
	\begin{Bmatrix}
	\underline{F} \\
	\underline{M}
	\end{Bmatrix}
	= \underline{\underline{\mathcal{C}}} \begin{Bmatrix}
	\underline{\epsilon} \\
	\underline{\kappa}
	\end{Bmatrix}
\end{align}
where $\underline{\underline{\mathcal{M}}}$ and
$\underline{\underline{\mathcal{C}}}$ are the $6 \times 6$ sectional mass
and stiffness matrices, respectively (note that they are not really tensors);
$\underline{\epsilon}$ and $\underline{\kappa}$ are the 1D strains and
curvatures, respectively; and, $\underline{\omega}$ is the angular velocity
vector that is defined by the rotation tensor $\underline{\underline{R}}$ as
$\underline{\omega} =
\mathrm{axial}(\dot{\underline{\underline{R}}}~\underline{\underline{R}}^T)$. The axial vector $\vec{a}$ associated with a second-order tensor $\tens{A}$ is denoted $\vec{a}=\mathrm{axial}(\tens{A})$ and its components are defined as
\begin{equation}
    \label{axial}
    \vec{a} = \mathrm{axial}(\tens{A})=\begin{Bmatrix}
    a_1 \\
    a_2 \\
    a_3
    \end{Bmatrix}
    =\frac{1}{2}
    \begin{Bmatrix}
    A_{32}-A_{23} \\
    A_{13}-A_{31} \\
    A_{21}-A_{12}
    \end{Bmatrix}
\end{equation}
The 1D strain measures are defined as
\begin{equation}
    \label{1DStrain}
    \begin{Bmatrix}
        \vec{\epsilon} \\
        \vec{\kappa}
    \end{Bmatrix}
    =
    \begin{Bmatrix}
        \vec{x}^\prime_0 + \vec{u}^\prime - (\tens{R} ~\tens{R}_0) \bar{\imath}_1 \\
        \vec{k}
    \end{Bmatrix}
\end{equation}
where $\vec{k} = \mathrm{axial}[(\tens{R R_0})^\prime (\tens{R R_0})^T]$ is the sectional
curvature vector resolved in the inertial basis and $\bar{\imath}_1$ is the unit
vector along $x_1$ direction in the inertial basis. Note that these
three sets of equations, including equations of motion
\eqref{GovernGEBT-1} and \eqref{GovernGEBT-2}, constitutive equations
\eqref{ConstitutiveMass} and \eqref{ConstitutiveStiff}, and kinematical
equations \eqref{1DStrain}, provide a full mathematical description of beam elasticity problems.

For a displacement-based finite-element implementation, there are six
degrees of freedom at each node: three displacement components and three
rotation components. Here, $\vec{q}$ denotes the elemental
displacement array as $\underline{q}^T=\left[
\underline{u}^T~~\underline{p}^T\right]$, where $\vec{u}$ is the
displacement and $\vec{p}$ is the rotation-parameter vector. The
acceleration array can thus be defined as $\underline{a}^T=\left[
\ddot{\underline{u}}^T~~ \dot{\underline{\omega}}^T \right]$. For nonlinear
finite-element analysis, the discretized forms of
displacement, velocity, and acceleration are written as
\begin{align}
	\label{DiscretizedDisp}
	\underline{q} (x_1) &= \underline{\underline{N}} ~\hat{\underline{q}}~~~~~~~~\underline{q}^T = \left[ \underline{u}^T~~\underline{p}^T \right] \\
	\label{DiscretizedVel}
	\underline{v}(x_1) &= \underline{\underline{N}}~\hat{\underline{v}}~~~~~~~~\underline{v}^T = \left[\underline{\dot{u}}^T~~\underline{\omega}^T \right] \\
	\label{DiscretizedAcc}
	\underline{a}(x_1) &= \underline{\underline{N}}~ \hat{\underline{a}}~~~~~~~~\underline{a}^T = \left[ \ddot{\underline{u}}^T~~\dot{\underline{\omega}}^T \right]
\end{align}
where $\tens{N}$ is the shape function matrix and $(\hat{\cdot})$ denotes a
column matrix of nodal values.

\subsection{Spatial and Temporal Discretization}

As described above, the GEBT model is discretized in space with LSFEs\cite{Ronquist-Patera:1987}, which are a type of high-order $p$-type finite element (FE).
Elements have $p+1$ nodes located at the Gauss-Lobatto-Legendre points, where $p$ is the polynomial order of the Lagrangian-interpolant basis functions.
For a given number of nodes, we have shown\cite{Wang:SFE2013,Wang:GEBT2014} that LSFEs for GEBT-based models can be dramatically more accurate than low-order elements.
As a tool in BeamDyn, an LSFE evaluated with an appropriate quadrature scheme provides the option to model a modern, highly flexible turbine blade with a single element.
The choice of numerical quadrature is described in the next section.
The GEBT-LSFE equations are time-integrated with a second-order-accurate generalized-alpha algorithm that is equipped with user-defined numerical damping (see Wang et al.\cite{Wang-etal:2015} for details).

\subsection{Finite-Element Quadrature}
\label{sec:quad}

Numerical integration (quadrature) of the finite-element inner products over an element domain is required in the FE formulation.
Typically, the quadrature rule employed in a finite-element implementation is Gauss-Legendre (GL), for which the number of quadrature points is chosen based on the polynomial order of the underlying FE basis functions.
In the case where material properties vary significantly over an element domain, the accuracy of the quadrature is degraded, which can affect the overall accuracy of the solution.   If the number of quadrature points is fixed to the FE basis-function order, accuracy is increased by either increasing number of elements ($h$-refinement) or the order of the elements ($p$-refinement).  However, if the quadrature order is chosen for accurate evaluation of FE inner products, then the choice in FE resolution can be chosen based on overall solution accuracy.

For wind turbine blade analysis, the material sectional properties are defined discretely at $n_s$ stations along the beam axis.
BeamDyn is equipped with two quadrature options: Gauss-Legendre quadrature and trapezoidal-rule (TR) quadrature.
For GL quadrature, $n_q= p + 1$, where $n_q$ is the number of quadrature points and $p$ is the order of the LSFE  (however, the GL quadrature point locations differ from the $p+1$ GLL nodal locations).  Material properties are linearly interpolated from the nearest-neighbor discrete stations.
Depending on the nature of the material properties, an increase in the element order $p$ could instigate a dramatically different response, because the quadrature points may capture different material properties.
For TR quadrature, $n_q = n_s + (n_s-1) \times (j-1)
= ( n_s - 1 ) \times j + 1$, where $j$ is a positive integer that is user specified.
Trapezoidal-rule quadrature enables a user to model a modern turbine blade defined by many cross-sectional property stations with few node points (i.e., $p \ll n_s$) while capturing all of the provided material properties.
For example,
the widely used NREL 5-MW reference wind turbine\cite{Jonkman-etal:2009} blade is defined by 49 stations along the blade axis.   If one were using first-order FEs with a fixed quadrature scheme, at least 48 elements would be required to accurately capture the material data in the FE inner products.
BeamDyn, with the GEBT model and LSFE $p$-type discretization, is equipped to model a wind turbine blade with a \textit{single} element.
LSFE discretization with TR quadrature
is an effective modeling approach when the beam deformation can be described accurately with relatively few FE nodes, despite the large number of material-property stations.    However, for a given element order and $n_q \gg p$, solutions will be more expensive than if $n_q \approx p$ because inner products are evaluated at least once per time step.

%The numerical analysis of wind turbine blade features a large number of cross-sectional data along the blade, usually ranging from dozens to more than one hundred stations. A typical practice in dealing with those cross-sectional data is interpolating a subset of the data to the quadrature point, for example, Gauss point. However, there are two drawbacks of this method. One is that it is very difficult to include all the data given the large number of cross sections used in wind turbine modeling.
%The widely used NREL 5-MW reference wind turbine blade, for example, provides 49 stations along the blade axis. If linearly discretizing this blade, 48 first-order finite elements, with two end points align with two adjacent stations, are needed to make sure that all the cross-sectional data being used in the simulation. The other drawback is that there is no rigorous way to interpolate the station data to the quadrature points.

% Given the discussion above, we implemented a new trapezoidal quadrature into BeamDyn, see Eq.~\ref{eqn:Trapezoidal}.
% \begin{equation}
%     \label{eqn:Trapezoidal}
%     \int_a^b f(x) dx \approx \displaystyle \frac{1}{2} \sum_{k=1}^{N}(x_{k+1}-%x_k)\left[f(x_{k+1}) + f(x_k)\right]
% \end{equation}
% where $N$ is the total number of intervals. Generally, this is a over-integration scheme by assuming that each station point is a quadrature point regardless of the number of the finite element nodes. Based on this assumption, all the cross-sectional data are included in the calculation and no interpolation is needed since the quadrature points are exactly located on the cross-sectional station points.

\subsection{Module-Coupling Algorithm}
%For clarification, ``implicit" here refers to a module that needs information from other modules before the solution can be time-advanced; the modules are thus tied together in a linear or nonlinear system that must be solved for time advancement.

The FAST modularization framework\cite{Jonkman:2013,Sprague:2014,Sprague:FAST2015} was created to loosely couple multi-physics modules for time-domain simulation.
Each module's internal variables are described by parameters (as constants) and states that are either continuous-in-time, discrete-in-time, constraints, or ``other'' (e.g., logicals).
Modules interact through input-output relationships, where output quantities are derived from inputs and states, and where inputs and outputs are defined on spatial meshes, and a predictor-corrector algorithm is employed to improve stability and accuracy of the time-update of the coupled system.
The modularization environment provides utilities for coupling non-matching meshes in space and time.
A detailed description of the coupling algorithm employed here can be found in Sprague et al.\cite{Sprague:FAST2015}, which we summarize as follows for the simplified case where each module is advanced with the same time increment $\Delta t$, and where we ignore details regarding the mapping of information between modules with non-matching meshes.
Assume that we know all states, inputs, and outputs at time $t$.  In order to advance the states of all modules from time $t$ to $t+ \Delta t$,
\begin{enumerate}

\item Using linear or quadratic extrapolation of known inputs, approximate the inputs at $t+\Delta t$.

\item Update the states of all modules to $t + \Delta t$.

\item Solve the global system of input-output equations at $t + \Delta t$.  Depending on the relationship between modules and the module output equations, this system solve can range from a simple transfer of information to a full nonlinear-system solve.

\item Either accept the states, inputs, and outputs, or apply a correction by repeating step (2) with the inputs calculated in Step (3), and then repeating Step (3).

\end{enumerate}
It was shown, using simple numerical examples, in Sprague et al.\cite{Sprague:2014,Sprague:FAST2015} that employing one or more correction steps can increase the accuracy of the coupled simulation and can increase stability by permitting the use of larger time increments.  However, these increases in accuracy and stability must be weighed against the additional cost of additional ``update state'' and output calculations for each module.

\section{Numerical Examples}

\subsection{Example 1: Partitioned Analysis}

In our first example, we have an oversimplified representation of a blade connected to a hub, where
a BeamDyn beam is ``clamped'' to the mass in a spring-damper-mass (SDM) system as shown in Figure~\ref{fig:CoupledSystem}.
We use this example to verify our numerical implementation and examine the accuracy and numerical behavior of the coupling algorithm.
Here, the relevant output from BeamDyn is translation reaction force, whereas its inputs are root motion (translational displacement, velocity, and acceleration).
The inputs and outputs are the same, but swapped, for the SDM system.
The time integrator in BeamDyn is a second-order implicit generalized-alpha algorithm (see Wang et al.\cite{Wang-etal:2015}), and the time integrator for the SDM system is fourth-order Adams-Bashforth-Moulton (ABM4), which is a predictor-corrector algorithm.
The material properties, coordinate system, and geometric parameters can be found in Figure~\ref{fig:CoupledSystem}.
The cross-section dimensions of the beam is $0.1$ m $\times$ 0.1 m.
The natural frequency of the uncoupled mass-spring-damper system is $6.28$ rad/s and the first five distinct natural frequencies for the uncoupled beam (in a clamped/cantilevered configuration) are $0.26, 1.72, 5.78, 22.62$, and $24.21$ rad/s, respectively, as determined by a refined ANSYS$^{\circledR}$ Mechanical modal analysis.
The first five distinct natural frequencies of the coupled system, obtained by ANSYS modal analysis, are $0.25, 0.85, 1.80, 4.76$, and $9.34$ rad/s, respectively.
For time-domain analysis, the system has quiescent initial conditions with an initial displacement of $0.1$ m from the neutral position in the positive $Z$ direction (see Figure ~\ref{fig:CoupledSystem}).

\begin{figure}
\centering
\includegraphics[clip,width=0.6\textwidth]{\directory CoupledSystem.pdf}
\caption{Schematic showing a 0.1 m $\times$ 0.1 m beam clamped to a spring-damper-mass system with system properties and dimensions.}
\label{fig:CoupledSystem}
\end{figure}

For verification, we analyzed the system in ANSYS using 60 BEAM188 elements (cantilevered to a point mass with spring and damper elements) and the time increment was $10^{-5}$ s. The Newmark-$\beta$ time integrator is adopted in ANSYS without numerical damping.
For the BeamDyn results, the beam was discretized by a single eighth-order element, and the time increment was  $10^{-5}$ s.  BeamDyn inner products were evaluated with standard Gauss-Legendre quadrature.
Unless otherwise noted, BeamDyn numerical damping was disabled and there was no correction step in the coupling algorithm.
Based on initial conditions corresponding to translating the mass and beam 0.1-m along Z and letting go, the root and tip displacements of the beam calculated with the two models are shown in Figure~\ref{fig:E1Disp}.
Excellent agreement is demonstrated between the ANSYS and BeamDyn results.
The root horizontal velocity and acceleration are also compared between the current model and the ANSYS benchmark solution in Figure~\ref{fig:E1VelAcc}.
Again, excellent agreement is shown.

\begin{figure}
    \centering
    \begin{tabular}{c}
    \subfloat[Root Displacement]{\label{fig:E1DispRoot}\includegraphics[clip,width=0.4\textwidth]{\directory  Disp_Root.pdf}} \qquad
\subfloat[Tip Displacement]{\label{fig:E1DispTip}\includegraphics[clip,width=0.4\textwidth]{\directory  Disp_Tip.pdf}}\\
\end{tabular}
\caption{Beam-root and -tip horizontal-displacement histories calculated with ANSYS and BeamDyn for the beam-SDM system (Figure~\ref{fig:CoupledSystem}).  The ANSYS and BeamDyn models were both time integrated with $\Delta t = 10^{-5}$ s. The ANSYS model had 60 BEAM188 elements, and the BeamDyn model had a single eighth-order element.}
\label{fig:E1Disp}
\end{figure}

\begin{figure}[h!tp]
    \centering
    \begin{tabular}{c}
    \subfloat[Root Velocity]{\label{fig:E1VelRoot}\includegraphics[clip,width=0.4\textwidth]{\directory  Vel_Root.pdf}} \qquad
\subfloat[Root Acceleration]{\label{fig:E1AccRoot}\includegraphics[clip,width=0.4\textwidth]{\directory  Acc_Root.pdf}}\\
\end{tabular}
\caption{Beam-root horizontal velocity and acceleration histories calculated with ANSYS and BeamDyn for the beam-SDM system (Figure~\ref{fig:CoupledSystem}).  Both models were time integrated with $\Delta t = 10^{-5}$ s. The ANSYS model had 60 BEAM188 elements, and the BeamDyn model had a single eighth-order element.}
\label{fig:E1VelAcc}
\end{figure}

For the BeamDyn-SDM system, we consider first stability of the coupling algorithm for which the beam is discretized by a single fifth-order LSFE.
Figure~\ref{fig:CoupledDTPC} shows the maximum stable time increments for the BeamDyn-SDM system, obtained by numerical experiments against the number of correction iterations in the FAST loose-coupling scheme.  Results are shown with and without numerical damping in the BeamDyn module.
Increasing the number of correction iterations increases the allowable time increment for stability for up to three corrections.  In moving from three to four corrections, however, we see no increase in stability for the numerical-damping-free simulations, and we even see a small decrease in stability with BeamDyn numerical damping.  Overall, however, the inclusion of numerical damping in BeamDyn increases the allowable time increment for stable solutions.
The computational savings offered by a larger stable time increment provided by additional correction steps must be considered against the additional computational cost of the correction steps.
For example, adding a single correction step makes each time step about twice as expensive compared with no correction.
We also note that the maximum stable time increment for the uncoupled BeamDyn model (without numerical damping) is $5 \times 10^{-3}$ s.  Thus, the coupled-system is more stiff.

\begin{figure}[h!tp]
\centering
\includegraphics[clip,width=0.5\textwidth]{\directory dt_pc.pdf}
\caption{Maximum stable time increments as a function of the number of correction iterations in the predictor-corrector coupling algorithm for the BeamDyn-SDM system. Results are shown with ($\rho_\infty = 0.9$) and without ($\rho_\infty = 1.0$) numerical damping in the BeamDyn module.
}
\label{fig:CoupledDTPC}
\end{figure}

We consider here the accuracy of the coupled model for the BeamDyn-SDM system by examining solutions calculated with various spatial and temporal discretization levels.
Accuracy was quantified by root-mean-square (RMS) error, $\varepsilon_{RMS}$,  which was calculated as
\begin{equation}
\varepsilon_{RMS}=\sqrt{\frac{\sum_{k=0}^{n_{max}}[U_z^k-U_b(t^k)]^2}{\sum_{k=0}^{n_{max}}[U_b(t^k)]^2}}
\label{RMSdefi}
\end{equation}
and where $U_b(t)$ is the benchmark solution calculated with a BeamDyn-SDM solution calculated with a 9-node BeamDyn model and $\Delta t = 5 \times 10^{-6}$ s. Figure~\ref{fig:ConvDTNode} shows the RMS error of the horizontal tip displacements as a function of the total number of BeamDyn nodes for two different time increments ($1 \times 10^{-4}$ s and $2 \times 10^{-4}$ s); BeamDyn had no numerical damping, and there were no correction steps in the loose-coupling algorithm.  The results indicate that $\Delta t = 2 \times 10^{-4}$ s provides time-increment-independent solutions.
We see approximately second-order convergence with spatial refinement (increase in the number of nodes), and results indicate that a fifth-order element (six nodes) provides results of sufficient accuracy.
Table~\ref{tab:RMS_pc} shows RMS errors of tip-displacement histories for the BeamDyn-SDM system for several combinations of time increment and number of corrections in the loose-coupling algorithm. We see that, for this system, increasing the number of corrections permits larger time increments while maintaining sufficient accuracy.

\begin{figure}[h!tp]
\centering
\includegraphics[clip,width=0.5\textwidth]{\directory RMS_dt_node.pdf}
\caption{Normalized RMS error of horizontal tip displacement histories as a function of total number of nodes for three different time increments. The dashed line shows ideal second-order convergence. The benchmark solution was calculated with a 9-node LSFE and $\Delta t = 5 \times 10^{-6}$ s.}
\label{fig:ConvDTNode}
\end{figure}

\begin{table}[h!tp]
    \centering
    \begin{tabular}{| c | c | c |}
    \hline
    $\Delta t$ (s) & \# of corrections & $\varepsilon_{RMS}$ \\ \hline
    2 $\times 10^{-4}$ & 0 & 3.85 $\times 10^{-2}$ \\ \hline
    5 $\times 10^{-4}$ & 1 & 5.78 $\times 10^{-2}$ \\ \hline
    1 $\times 10^{-3}$ & 2 & 3.85 $\times 10^{-2}$ \\ \hline
    3 $\times 10^{-3}$ & 3 & 3.83 $\times 10^{-2}$ \\ \hline
    3 $\times 10^{-3}$ & 4 & 3.82 $\times 10^{-2}$ \\
    \hline
    \end{tabular}
    \caption{RMS errors of tip-displacement histories for the BeamDyn-SDM system for several combinations of time increment and number of corrections in the loose-coupling algorithm.  Results are shown for a BeamDyn model with 6 nodes and no numerical damping.}
    \label{tab:RMS_pc}
\end{table}

\subsection{Example 2: NREL 5-MW Wind Turbine}

The second example is an analysis of the NREL 5-MW reference wind turbine\cite{Jonkman-etal:2009}, which has straight 61.5 m blades.
We examine simulation results where the three blades are modeled by BeamDyn or ElastoDyn.
The blade structural-dynamics model in the ElastoDyn module of FAST is applicable to straight isotropic blades dominated by bending.  The model includes two flapwise-bending modes and one edgewise-bending mode, coupled through a structural pre-twist, but neglects axial, shear, and torsional degrees of freedom and mass and elastic offsets from the pitch axis.  Several geometric and kinematic nonlinearities are accounted for, including radial shortening and centrifugal, Coriolis, and gyroscopic loading.

We examine here the numerical performance of two different BeamDyn quadrature methods (c.f., Section~II.C), Gauss-Legendre and trapezoidal, for this realistic-blade analysis.
As described above, the sectional properties for the NREL 5-MW reference turbine blade are defined at 49 stations along its 61.5 m length.
First, a cantilevered blade under a uniformly distributed static force of magnitude $10^4$ N/m along the flap direction is analyzed.
Figure~\ref{fig:5MWStaticTip} shows the tip displacement in the flap direction as a function of the number of nodes.
Monotonic convergence of tip-displacement is shown for the trapezoidal-quadrature results with an increasing number of nodes.
The convergence rate of tip displacements for Gauss-Legendre quadrature, however, is non-monotonic.  As described in Section~II.C, the trapezoidal-rule quadrature captures all 49 material-data stations regardless of the number of element nodes, whereas the particular material data incorporated by Gauss-Legendre quadrature varies with the number of element nodes.
This is also demonstrated in the calculation of total blade mass as shown in Figure~\ref{fig:5MWMass}.
The total blade mass as calculated with trapezoidal quadrature is independent of the number of nodes, whereas the mass calculated by Gauss quadrature  depends on the number of nodes in the
element, and a large number of nodes is required for an accurate total-mass
calculation.
We note that a small scaling factor has been applied to the calculation of blade mass so that it is consistent with ElastoDyn.
In all subsequent calculations with BeamDyn, trapezoidal quadrature is employed.
\begin{figure}
\centering
\includegraphics[clip,width=0.5\textwidth]{\directory 5MW_Static_Tip.pdf}
\caption{Tip deflections of a cantilevered NREL 5-MW blade under uniformly distributed load as a function of the number of nodes in a single-element BeamDyn model where finite-element inner-products were calculated with Gauss-Legendre or trapezoidal-rule quadrature.}
\label{fig:5MWStaticTip}
\end{figure}

\begin{figure}
    \centering
\includegraphics[clip,width=0.5\textwidth]{\directory  5MW_Mass_Scaled.pdf}
\caption{Total blade mass of an NREL 5-MW reference blade as a function of the number of nodes in a single-element BeamDyn model where finite-element inner-products were calculated with Gauss-Legendre or trapezoidal-rule quadrature.}
\label{fig:5MWMass}
\end{figure}

Next, we studied the time step sizes required for stable simulation of BeamDyn in stand-alone and couple-to-FAST modes. Figure~\ref{fig:5MWdt_node} shows the maximum time step size versus the number of nodes for a BeamDyn model composed of a single element. In the stand-alone mode, we used FAST as the driver but with all coupling options disabled so that the blade rotated at a fixed speed loaded only by gravity. For the coupled-to-FAST case, we conducted an aero-servo-elastic wind turbine analysis under a mean wind speed of $12$ m/s$^2$ with turbulence, which is  certification test case \#26 in the FAST archive.  BeamDyn numerical damping was disabled and there were no correction iterations in the coupling algorithm.  We see that the two-way coupling between BeamDyn and FAST requires significantly smaller time increments for stable solutions.

\begin{figure}
    \centering
\includegraphics[clip,width=0.5\textwidth]{\directory  5MW_dt_node.pdf}
\caption{Maximum stable time increments vs number of FE nodes for an NREL 5-MW blade, where the blade is modeled with a single element.}
\label{fig:5MWdt_node}
\end{figure}

Finally, we studied the performance of BeamDyn in the coupled FAST analysis. Figures~\ref{fig:5MW_Coupled_dt} and \ref{fig:5MW_Coupled_Mesh} show the tip flap displacement histories under different time and space discretizations. Note that all of the quantities studied here are defined in the body-attached blade reference coordinate system following the IEC standard, where the $X$ direction is toward the suction side of the airfoil, the $Y$ direction is toward the trailing edge, and the $Z$ direction is toward the blade tip from root. These results demostrated that, for this system, results that are grid independent (in space and time) can be obtained with $\Delta t = 2 \times 10^{-3}$ s  and a single fifth-order element for each blade.

\begin{figure}
    \centering
\includegraphics[clip,width=0.9\textwidth]{\directory  5MW_Coupled_dt.pdf}
\caption{Blade tip deflection histories along flap direction obtained using different time increments, where the blade is modeled with a single fifth-order element.}
\label{fig:5MW_Coupled_dt}
\end{figure}

\begin{figure}
    \centering
\includegraphics[clip,width=0.9\textwidth]{\directory  5MW_Coupled_Mesh.pdf}
\caption{Blade tip deflection histories along flap direction obtained using different BeamDyn refinements, where models were time integrated with $\Delta t = 2 \times 10^{-3}$ s.}
\label{fig:5MW_Coupled_Mesh}
\end{figure}

We compared the results obtained by BeamDyn with those obtained by ElastoDyn for the coupled analysis (as described above).  The BeamDyn blades were each modeled with a single fifth-order element, and the FAST-BeamDyn system was time-integrated with $\Delta t = 2 \times 10^{-3}$ s as demanded for numerical stability.
The FAST-ElastoDyn system was time integrated with $\Delta t = 1.25 \times 10^{-2}$ s.
The tip displacements of blade 1 are shown in Figure~\ref{fig:5MWTip}.  Results for BeamDyn are shown with and without off-diagonal terms in the sectional mass matrices (the latter is for more direct comparison to ElastoDyn).
Good agreement can be observed between the ElastoDyn and BeamDyn results. It is noted that because of the Trapeze effect and elastic stretching considered in BeamDyn, the mean value of the axial displacement in Figure~\ref{fig:5MWTipZ} by BeamDyn is different from ElastoDyn. Figure~\ref{fig:5MWRootF} and \ref{fig:5MWRootM} shows the root reaction forces and moments calculated by BeamDyn and ElastoDyn. Again, good agreement can be found in these quantities. We note the spurious spikes in the $M_{pitch}$ histories, which will be addressed in a future release of BeamDyn.  While we see noticeable differences in the tip displacement histories, it is interesting to note the excellent agreement between ElastoDyn and BeamDyn results for root reaction forces and moments.  This is because the NREL 5-MW blade features are well accommodated by the approximations behind the ElastoDyn model.  In particular,
ElastoDyn is well suited for modeling the NREL 5-MW blade because
\begin{itemize}
\item the blade is naturally straight,
\item the lowest modes excited by wind are dominated by bending,
\item there are no cross-sectional couplings induced by anisotropic composite laminate layups,
\item torsion, extension, and shear effects are mostly negligible (the first torsional mode natural frequency is well above rated rotor speed; the blade aspect ratio is high, etc.),
\item the deflections are moderate enough (not too large) that they can be accurately captured by the geometric nonlinear terms included in ElastoDyn, and
\item the mass-center offsets are small and do not cause a large change in response.
\end{itemize}
The benefits of moving from an efficient model like ElastoDyn to a higher-fidelity model that is more computationally expensive like BeamDyn will be best seen for turbine blades that do not satisfy the simplifying features, e.g., those with aero-elastically tailored curved blades.\cite{Sri:SciTech2016}

\begin{figure}
    \centering
    \subfloat[Flap direction]{\label{fig:5MWTipX}\includegraphics[clip,width=0.9\textwidth]{\directory  B1TipTDxr.pdf}} \\
\subfloat[Edge direction]{\label{fig:5MWTipY}\includegraphics[clip,width=0.9\textwidth]{\directory  B1TipTDyr.pdf}}\\
\subfloat[Axial direction]{\label{fig:5MWTipZ}\includegraphics[clip,width=0.9\textwidth]{\directory  B1TipTDzr.pdf}}\\
\caption{Comparisons of blade 1 tip displacements between ElastoDyn and BeamDyn results.}
\label{fig:5MWTip}
\end{figure}

\begin{figure}[h!t]
    \centering
    \subfloat[$F_{flap}$]{\label{fig:5MWRootFx}\includegraphics[clip,width=0.8\textwidth]{\directory  B1RootFxr.pdf}} \\
\subfloat[$F_{edge}$]{\label{fig:5MWRootFy}\includegraphics[clip,width=0.8\textwidth]{\directory  B1RootFyr.pdf}}\\
\subfloat[$F_{axial}$]{\label{fig:5MWRootFz}\includegraphics[clip,width=0.8\textwidth]{\directory  B1RootFzr.pdf}} \\
\caption{Comparisons of root reaction forces between ElastoDyn and BeamDyn results}
\label{fig:5MWRootF}
\end{figure}

\begin{figure}[h!t]
    \centering
\subfloat[$M_{edge}$]{\label{fig:5MWRootMx}\includegraphics[clip,width=0.8\textwidth]{\directory  B1RootMxr.pdf}}\\
\subfloat[$M_{falp}$]{\label{fig:5MWRootMy}\includegraphics[clip,width=0.8\textwidth]{\directory  B1RootMyr.pdf}} \\
\subfloat[$M_{pitch}$]{\label{fig:5MWRootMz}\includegraphics[clip,width=0.8\textwidth]{\directory  B1RootMzr.pdf}}
\caption{Comparisons of root reaction moments between ElastoDyn and BeamDyn results}
\label{fig:5MWRootM}
\end{figure}

\section{Conclusion}

In this paper, we examined the beam theory and the coupling algorithm for partitioned mechanical-system analysis. The governing equations for BeamDyn were reviewed and the coupling schemes between BeamDyn and other mechanical modules were explained. For wind turbine blade analysis, which features a large number of cross-sectional stations along the blade axis, we implemented a trapezoidal numerical integration so that all the cross-sectional data, including inertial and stiffness matrices, can be used in the analysis. The users do not need to pick certain stations from a large set of data nor numerically interpolate them, which usually introduces errors. This new spatial integration method and the coupling algorithm have been verified in the numerical examples. The results obtained by trapezoidal quadrature were monotonously converged while those obtained by conventional Gauss integration were randomly converged depending on the stations chosen and interpolation method. In the coupled analysis, exponential convergence rate can be seen in the BeamDyn results, which is a prominent feature of the LSFEs. In the full turbine analysis, reasonable agreement between BeamDyn and ElastoDyn results is found. More verification and validation results can be found in Guntar et al.\cite{Sri:SciTech2016}.

\section*{Acknowledgments}

This work was supported by the U.S. Department of Energy under Contract No.\
DE-AC36-08GO28308 with the National Renewable Energy Laboratory. Funding for the work was provided by the DOE Office of Energy Efficiency and Renewable Energy, Wind and Water Power Technologies Office.

\bibliographystyle{aiaa}
\bibliography{references}

\end{document}


% !TEX root = ParticleFilter.tex
\section{Discussion and Future Work\label{discussion}}

\subsection{Discussion}

This paper has experimented with the use of a sequential importance resampling (SIR) particle filter (PF) as a means of dynamically incorporating data into a simple agent-based model of a pedestrian system. The results demonstrate that it is possible to use a particle filter to perform dynamic adjustment of the model. However, they also show that (as expected~\citep{rebeschini_can_2015, carrassi_data_2018}) as the dimensionality of the system increases, the number of particles required to maintain an acceptable approximation error grows exponentially. The reason for this is because, as the dimensionality increases, it becomes less likely that an individual particle will have the `correct combination' of values~\citep{long_spatial_2017}. In this work, the dimensionality is proportional to the number of agents. At most 10,000 particles were used, which was sufficient for a simulation with 30-40 agents. However, for a more complex and realistic model containing hundreds or thousands of agents, the number of particles required would most likely number in the millions. The particle filter used in this study was provided with more information than would normally be available. For example, information was supplied as fixed parameters on when each agent entered the simulation, their maximum speeds, and their chosen destinations. Therefore the only information that the particle filter was lacking was the actual locations of the agents and whether they would chose to move left or right to prevents a collision with another agent. It is entirely possible to include these agent-level parameters in the state vector, but this would further increase the size of the state space and hence the number of particles required. This is an important caveat as in a real-world situation it is very unlikely that such detail would be available. Future work should begin to experiment with the number of particles that would be required when observational data that are more akin to those available in the real world are used.

There are a number of possible improvements that could be made to the basic SIR particle filter to reduce the number of particles required. For example, \citep{wang_data_2015} propose \textit{component set resampling} -- details below -- but exploring these further is beyond the scope of this paper. Overall, these results reflect those of other studies which demonstrate that particle filtering has value for simulations with relatively few agents and interactions \citep[e.g.][]{wang_data_2015, lueck_who_2019, kieu_dealing_2019} but that the large dimensionality of a pedestrian system poses problems for the standard (unmodified) bootstrap filter.

\subsection{Improvements to the particle filter}

There are a number of adaptions to the main particle filtering algorithm that might make the method more amenable to use with complex agent-based models. The aforementioned Component Set Resampling \citep{wang_data_2015} approach proposes that individual components of particles are sampled, rather than whole particles in their entirety. A more commonly used approach is to reduce the dimensionality of the problem in the first place. With spatial agent-based models, such as the one used here, spatial aggregation provides such an opportunity. In the data assimilation stage, the state vector could be converted to an aggregate form, such as a population density surface, and particle filtering could be conducted on the aggregate surface rather than on the individual locations of the agents. After assimilation, the particles could be disaggregated and then run as normal. This will, of course, introduce error because the exact positions of agents in the particles will not be known when disaggregating, but that additional uncertainty might be outweighed by the benefits of a more successful particle filtering overall. In addition, the observations that could be presented to an aggregate particle filter might be much more in line with those that are available in practice (as will be discussed shortly). If the aim of real-time model calibration is to give decision makers a \textit{general idea} about how the system is behaving in real time, then this additional uncertainty might not be problematic. A similar approach, proposed by \citep{rebeschini_can_2015}, could be to divide up the space into smaller regions and then apply the algorithm locally to these regions (i.e. a divide-and-conquer approach). Although it is not clear how well divide-and-conquer would work in an agent-based model -- for example, \citep{long_spatial_2017} developed the method for a discrete cellular automata model -- it would be an interesting avenue to explore.

\subsection{Real-World Implications and Future Work}

It is important to note that, unlike other data assimilation approaches, the particle filter does not dynamically alter the state of the running model. This could be advantageous because, with agent-based modelling, it is not clear that the state of an agent should be manipulated by an external process. Agents typically have goals and a history, and behavioural rules that rely on those features, so artificially altering an agent's internal state might disrupt their behaviour making it, at worst, nonsensical. Experiments with alternative (potentially more efficient) algorithms such as 4DVar or a the Ensemble Kalman Filter should be conducted to test this.

Ultimately the aim of this work is to develop methods that will allow simulations of human pedestrian systems to be optimised in real time. Not only will such methods provide decision makers with more accurate information about the present, but they could also allow for better predictions of the near future \citep[c.f.][]{kieu_dealing_2019}. One assumption made throughout the paper, which limits its direct real-world use, is that the locations of pseudo-real individuals are known, albeit with some uncertainty. Not only is this assumption unrealistic -- it is rare for individuals to be tracked to this degree in public places -- but we would also argue that the privacy implications of tracking individual people are not outweighed by the benefits offered by better understanding the system. Therefore, immediate future work will test how well a data assimilation algorithm would fare were it supplied only aggregate information such as the number of people who pass through a barrier, or the number of people recorded by a CCTV camera within a particular area. Both of these measures can be used in aggregate form and would be entirely anonymous. It is unclear whether such aggregate data would be sufficient to identify a `correct' agent-based model, so experiments should explore the spatio-temporal resolution of the aggregate data are required. Also, identifiability/equifinality analysis might help initially as a means of estimating whether the available data are sufficient to identify a seemingly `correct' model in the first place. In the end, such research might help to provide evidence to policy makers for the number, and characteristics, of the sensors that would need to be installed to accurately simulate the target system, and how these could be used to maintain the privacy of the people who they are recording.


\documentclass[12pt]{article}

\usepackage[utf8]{inputenc}
\usepackage[greek, english]{babel}

% Packages
\usepackage{alphabeta}
\usepackage{amsmath}
\usepackage{amsthm}
\usepackage{caption}
\usepackage{color}
\usepackage{float}
\usepackage{fullpage}
\usepackage{graphicx}
\usepackage{hyperref}
\usepackage{latexsym}
\usepackage{listings}
\usepackage{pxfonts}
\usepackage{stackrel}
\usepackage{subfig}
\usepackage{tikz}
\usepackage{titlesec}

% Commands
\newcommand{\N}{\mathbb{N}}
\newcommand{\R}{\mathbb{R}}
\newcommand{\abs}[1]{\left\lvert#1\right\rvert}
\newcommand{\code}[2]{\lstinputlisting[caption={#2}]{#1}}
\newcommand{\margin}{\hspace{4pt}}
\newcommand{\norm}[1]{\left\lVert#1\right\rVert}

% Environments
\newenvironment{matlab}
	{\begin{figure}[hp]\centering\captionsetup{justification=centering}}
	{\end{figure}}

\newenvironment{rcases}
	{\left.\begin{aligned}}
	{\end{aligned}\right\rbrace}

% Python Syntax Highlighting
\definecolor{string_color}{RGB}{0, 161, 13}
\definecolor{comment_color}{RGB}{46, 46, 46}
\definecolor{keyword_color}{RGB}{0, 112, 191}
\definecolor{background_color}{RGB}{250, 250, 250}

\lstset{
    framesep=15pt,
    xleftmargin=15pt,
    xrightmargin=15pt,
    language=Python,
    captionpos=b,
    numbers=right,
    numberstyle=\small\ttfamily,
    frame=lines,
    showspaces=false,
    showtabs=false,
    breaklines=true,
    showstringspaces=false,
    breakatwhitespace=true,
    commentstyle=\color{comment_color}\textit,
    keywordstyle=\bfseries\color{keyword_color}\textbf,
    stringstyle=\color{string_color}\textit,
    morekeywords={self, lambda, __init__, __del__, __name__, for, in, not, and, or, :},
    basicstyle=\small\ttfamily,
    tabsize=4,
    keepspaces=true,
    columns=flexible,
    backgroundcolor=\color{background_color}
}

% Links
\hypersetup{
    colorlinks=true,
    linkcolor=blue,
    filecolor=magenta,
    urlcolor=cyan,
}

% Lengths
\setlength{\parindent}{0in}
\setlength{\oddsidemargin}{0in}
\setlength{\textwidth}{6.5in}
\setlength{\textheight}{10in}
\setlength{\topmargin}{-1.0in}
\setlength{\headheight}{18pt}

\titlespacing*{\subsection}
{0pt}{5.5ex plus 1ex minus .2ex}{4.3ex plus .2ex}

\title{\huge Python Implementation}
\author{Sioros Vasileios\\ Andrinopoulou Christina}
\date{January 2020}

\begin{document}

\maketitle

\pagenumbering{gobble}

\pagebreak

\section{Modeling Sudoku Variations in Python}

The implementation was based on the paper \href{https://www.researchgate.net/publication/228615106_An_integer_programming_model_for_the_sudoku_problem}{\textit{"An Integer Programming Model
for the Sudoku Problem"}}. \\

We are going to be using the \href{https://pythonhosted.org/PuLP/}{\textbf{PuLP}} python library,
in order to model the given problems in terms of Linear Programming. \\

\subsection{The Classic Sudoku Solver class}

\begin{lstlisting}[caption={Initializing the \textbf{LpProblem} super class as a minimization problem}]
    self.matrix = matrix
    self.n = len(matrix)
    self.m = int(sqrt(self.n))

    super().__init__(
        name=f"{type(self).__name__}_solver_{self.n}_x_{self.n}".lower(),
        sense=LpMinimize
    )
\end{lstlisting}

\begin{lstlisting}[caption={Declaring our variables}]
    self.x = [
        [
            [
                LpVariable(
                    f"x_{i + 1:02d}_{j + 1:02d}_{k + 1:02d}", cat=LpBinary)
                for k in range(self.n)
            ] for j in range(self.n)
        ] for i in range(self.n)
    ]
\end{lstlisting}

\begin{lstlisting}[caption={Declaring the objective function}]
    self += 0
\end{lstlisting}

\pagebreak

\begin{lstlisting}[caption={Declaring that there should only be one \textit{k} in each column}]
    for j in range(self.n):
        for k in range(self.n):
            self += lpSum([self.x[i][j][k]
                            for i in range(self.n)]) == 1, f"in column {j + 1:02d} only one {k + 1:02d}"
\end{lstlisting}

\begin{lstlisting}[caption={Declaring that there should only be one \textit{k} in each row}]
    for i in range(self.n):
        for k in range(self.n):
            self += lpSum([self.x[i][j][k]
                            for j in range(self.n)]) == 1, f"in row {i + 1:02d} only one {k + 1:02d}"
\end{lstlisting}

\begin{lstlisting}[caption={Declaring that there should only be one \textit{k} in each submatrix}]
    for k in range(self.n):
        for p in range(self.m):
            for q in range(self.m):
                self += lpSum([
                    [
                        lpSum([
                            self.x[i][j][k]
                            for i in range(self.m * p, self.m * (p + 1))
                        ])
                    ]
                    for j in range(self.m * q, self.m * (q + 1))
                ]) == 1, f"in submatrix {p + 1:02d} {q + 1:02d} only one {k + 1:02d}"
\end{lstlisting}

\begin{lstlisting}[caption={Declaring that there should only be one \textit{k} in each cell}]
    for i in range(self.n):
        for j in range(self.n):
            self += lpSum([self.x[i][j][k]
                            for k in range(self.n)]) == 1, f"cell {i + 1:02d} {j + 1:02d} must be assigned exactly one value"
\end{lstlisting}

\pagebreak

\begin{lstlisting}[caption={Declaring that some cells have already received some initial values}]
    for i in range(self.n):
        for j in range(self.n):
            if self.matrix[i][j]:
                self += self.x[i][j][self.matrix[i][j] -
                                        1] == 1, f"cell {i + 1:02d} {j + 1:02d} has an initial value of {self.matrix[i][j]:02d}"
\end{lstlisting}

\begin{lstlisting}[caption={Performing some preprocessing and invalidating some of the candidate values of each cell}]
    for i in range(self.n):
        for j in range(self.n):
            if not self.matrix[i][j]:
                for value in self.illegal_values(i, j):
                    self += (
                        self.x[i][j][value - 1] == 0,
                        f"cell {i + 1:02d} {j + 1:02d} cannot be assigned a value of {value:02d}"
                    )
\end{lstlisting}

\begin{lstlisting}[caption={Calling the LpProblem solve method and reflecting the result on the two-dimensional array "matrix"}]
    def solve(self, solver=None, **kwargs):

        super().solve(solver=solver, **kwargs)

        if LpStatus[self.status] != "Optimal":
            raise ValueError(
                f"Solver failed with status '{LpStatus[self.status]}'")

        for i in range(self.n):
            for j in range(self.n):
                if not self.matrix[i][j]:
                    self.matrix[i][j] = [
                        self.x[i][j][k].varValue for k in range(self.n)
                    ].index(1) + 1
\end{lstlisting}

\pagebreak

\begin{lstlisting}[caption={Checking for invalid candidate values before even calling LpProblem.solve}]
    def illegal_values(self, row, col):

        values = set()

        for j in range(self.n):
            if self.matrix[row][j] is not None:
                values.add(self.matrix[row][j])

        for i in range(self.n):
            if self.matrix[i][col] is not None:
                values.add(self.matrix[i][col])

        p, q = row // self.m, col // self.m

        for i in range(self.m * p, self.m * (p + 1)):
            for j in range(self.m * q, self.m * (q + 1)):
                if self.matrix[i][j] is not None:
                    values.add(self.matrix[i][j])

        return values
\end{lstlisting}

\pagebreak

If we now print an instance of the previously defined class
we get the following output. \\

\begin{lstlisting}[caption={The objective function and constraints of SudokuLP}]
    sudokulp_solver_9_x_9:

    MINIMIZE
    0*__dummy + 0

    SUBJECT TO
    in_column_01_only_one_01: x_01_01_01 + x_02_01_01 + x_03_01_01 + x_04_01_01
    + x_05_01_01 + x_06_01_01 + x_07_01_01 + x_08_01_01 + x_09_01_01 = 1
    ...
    in_column_09_only_one_09: x_01_09_09 + x_02_09_09 + x_03_09_09 + x_04_09_09
    + x_05_09_09 + x_06_09_09 + x_07_09_09 + x_08_09_09 + x_09_09_09 = 1

    in_row_01_only_one_01: x_01_01_01 + x_01_02_01 + x_01_03_01 + x_01_04_01
    + x_01_05_01 + x_01_06_01 + x_01_07_01 + x_01_08_01 + x_01_09_01 = 1
    ...
    in_row_09_only_one_09: x_09_01_09 + x_09_02_09 + x_09_03_09 + x_09_04_09
    + x_09_05_09 + x_09_06_09 + x_09_07_09 + x_09_08_09 + x_09_09_09 = 1

    in_submatrix_01_01_only_one_01: x_01_01_01 + x_01_02_01 + x_01_03_01
    + x_02_01_01 + x_02_02_01 + x_02_03_01 + x_03_01_01 + x_03_02_01 + x_03_03_01
    = 1
    ...
    in_submatrix_03_03_only_one_09: x_07_07_09 + x_07_08_09 + x_07_09_09
    + x_08_07_09 + x_08_08_09 + x_08_09_09 + x_09_07_09 + x_09_08_09 + x_09_09_09
    = 1

    cell_01_01_must_be_assigned_exactly_one_value: x_01_01_01 + x_01_01_02
    + x_01_01_03 + x_01_01_04 + x_01_01_05 + x_01_01_06 + x_01_01_07 + x_01_01_08
    + x_01_01_09 = 1
    ...
    cell_09_09_must_be_assigned_exactly_one_value: x_09_09_01 + x_09_09_02
    + x_09_09_03 + x_09_09_04 + x_09_09_05 + x_09_09_06 + x_09_09_07 + x_09_09_08
    + x_09_09_09 = 1

    cell_01_08_has_an_initial_value_of_02: x_01_08_02 = 1
    ...
    cell_09_02_has_an_initial_value_of_01: x_09_02_01 = 1

    cell_01_01_cannot_be_assigned_a_value_of_02: x_01_01_02 = 0
    ...
    cell_09_09_cannot_be_assigned_a_value_of_09: x_09_09_09 = 0
\end{lstlisting}

\begin{lstlisting}[caption={The variables of SudokuLP}]
    VARIABLES
    __dummy = 0 Continuous
    0 <= x_01_01_01 <= 1 Integer
    ...
    0 <= x_09_09_09 <= 1 Integer
\end{lstlisting}

\pagebreak

\subsection{The Sudoku X Solver class}

\begin{lstlisting}[caption={Initializing the \textbf{SudokuLP}} super class]
    super().__init__(matrix)
\end{lstlisting}

\begin{lstlisting}[caption={Declaring that there should only be one \textit{k} in the positive diagonal}]
    for k in range(self.n):

        self += lpSum([
            self.x[r][r][k] for r in range(self.n)
        ]) == 1, f"in the diagonal only one {k + 1}"
\end{lstlisting}

\begin{lstlisting}[caption={Declaring that there should only be one \textit{k} in the negative diagonal}]
    for k in range(self.n):

        self += lpSum([
            self.x[r][self.n - 1 - r][k] for r in range(self.n)
        ]) == 1, f"in the anti diagonal only one {k + 1}"
\end{lstlisting}

\pagebreak

\subsection{The Four Square Sudoku Solver class}

\begin{lstlisting}[caption={Initializing the \textbf{SudokuLP}} super class]
    super().__init__(matrix)
\end{lstlisting}

\begin{lstlisting}[caption={Declaring that there should only be one \textit{k} in each shaded square}]
    for i in [1, self.n - self.m - 1]:
        for j in [1, self.n - self.m - 1]:
            for k in range(self.n):
                self += lpSum([
                    [
                        lpSum([
                            self.x[r][c][k]
                            for c in range(j, j + self.m)
                        ])
                    ]
                    for r in range(i, i + self.m)
                ]) == 1, f"in square {i + 1:02d} {j + 1:02d} {i + self.m:02d} {j + self.m:02d} only one {k + 1:02d}"
\end{lstlisting}

\pagebreak

\subsection{The Four Pyramid Sudoku Solver class}

\begin{lstlisting}[caption={Initializing the \textbf{SudokuLP}} super class]
    super().__init__(matrix)
\end{lstlisting}

\begin{lstlisting}[caption={Declaring that there should only be one \textit{k} in the first pyramid-shaped pyramid region}]
    for k in range(1, self.n + 1):
        self += lpSum([
            lpSum([
                self.x[r - 1][c - 1][k - 1]
                for c in range(self.m + r, self.n - r + 1)
            ]) for r in range(1, self.m + 1)
        ]) == 1, f"in pyramid 1 only one {k + 1}"
\end{lstlisting}

\begin{lstlisting}[caption={Declaring that there should only be one \textit{k} in the second pyramid-shaped pyramid region}]
    for k in range(1, self.n + 1):
        self += lpSum([
            lpSum([
                self.x[r - 1][c - 1][k - 1]
                for r in range(1 + c, self.n - self.m + 1 - c + 1)
            ]) for c in range(1, self.m + 1)
        ]) == 1, f"in pyramid 2 only one {k + 1}"
\end{lstlisting}

\pagebreak

\begin{lstlisting}[caption={Declaring that there should only be one \textit{k} in the third pyramid-shaped pyramid region}]
    for k in range(1, self.n + 1):
        self += lpSum([
            lpSum([
                self.x[r - 1][c - 1][k - 1]
                for c in range(self.n + self.m - 1 - r, r - self.m + 1)
            ]) for r in range(self.n - self.m + 1, self.n + 1)
        ]) == 1, f"in pyramid 3 only one {k + 1}"
\end{lstlisting}

\begin{lstlisting}[caption={Declaring that there should only be one \textit{k} in the fourth pyramid-shaped pyramid region}]
    for k in range(1, self.n + 1):
        self += lpSum([
            lpSum([
                self.x[r - 1][c - 1][k - 1]
                for r in range(self.n + self.m + 1 - c, c - 1 + 1)
            ]) for c in range(self.n - self.m + 1, self.n + 1)
        ]) == 1, f"in pyramid 4 only one {k + 1}"
\end{lstlisting}

\begin{lstlisting}[caption={Example Usage}]
    options = {
        "sdk": SudokuLP,
        "sdkx": SudokuXLP,
        "sdkfs": FourSquareSudokuLP,
        "sdkfp": FourPyramidSudokuLP
    }

    extension = path.splitext(args.load)[1][1:]

    matrix = load(args.load)
    problem = options[extension](matrix)
\end{lstlisting}

\pagebreak

\section{Generating Sudoku Puzzles}

\begin{lstlisting}[caption={Generating a new Sudoku Puzzle by transposing the supplied one}]
    def transpose(matrix):
        return list(map(list, [*zip(*matrix)]))
\end{lstlisting}

\begin{lstlisting}[caption={Generating a new Sudoku Puzzle by relabeling the values in the supplied one}]
    def relabel(matrix):
        replacements = {
            original: replacement
            for original, replacement in zip(
                range(1, len(matrix) + 1),
                np.random.permutation(range(1, len(matrix) + 1))
            )
        }

        for i in range(len(matrix)):
            for j in range(len(matrix)):
                if matrix[i][j] is not None:
                    matrix[i][j] = replacements[matrix[i][j]]

        return matrix
\end{lstlisting}

\pagebreak

\begin{lstlisting}[caption={Generating a new Sudoku Puzzle by block reordering the rows and columns of the supplied one}]
    def reorder(matrix):
        def swap_rows(matrix):

            m = int(sqrt(len(matrix)))

            for p in range(m):
                for q in range(m):
                    for r in range(m):
                        i1 = randint(m * p, m * (p + 1) - 1)
                        i2 = randint(m * p, m * (p + 1) - 1)

                        matrix[i1], matrix[i2] = matrix[i2], matrix[i1]

            return matrix

        return transpose(swap_rows(transpose(swap_rows(matrix))))
\end{lstlisting}

\begin{lstlisting}[caption={Example Usage}]
    methods = {
        "transpose": transpose,
        "relabel": relabel,
        "reorder": reorder
    }

    matrix = load(args.load)
    matrix = methods[args.method](matrix)

    dump(matrix, args.save)
\end{lstlisting}

\pagebreak

\section{The Sudoku (.sdk*) file format}

For anyone interested in the details of our implementation,
we now present how sudoku puzzles are being represented internally
as well as how one could load or save a two-dimensional array in .sdk* format. \\

\begin{lstlisting}[caption={A .sdk* file example}]
    9           # +---+---+---+---+---+---+---+---+---+
    1, 1, 5     # | 5 | 3 |   |   | 7 |   |   |   |   |
    1, 2, 3     # +---+---+---+---+---+---+---+---+---+
    1, 5, 7     # | 6 |   |   | 1 | 9 | 5 |   |   |   |
    2, 1, 6     # +---+---+---+---+---+---+---+---+---+
    2, 4, 1     # |   | 9 | 8 |   |   |   |   | 6 |   |
    2, 5, 9     # +---+---+---+---+---+---+---+---+---+
    2, 6, 5     # | 8 |   |   |   | 6 |   |   |   | 3 |
    3, 2, 9     # +---+---+---+---+---+---+---+---+---+
    3, 3, 8     # | 4 |   |   | 8 |   | 3 |   |   | 1 |
    3, 8, 6     # +---+---+---+---+---+---+---+---+---+
    4, 1, 8     # | 7 |   |   |   | 2 |   |   |   | 6 |
    4, 5, 6     # +---+---+---+---+---+---+---+---+---+
    4, 9, 3     # |   | 6 |   |   |   |   | 2 | 8 |   |
    5, 1, 4     # +---+---+---+---+---+---+---+---+---+
    5, 4, 8     # |   |   |   | 4 | 1 | 9 |   |   | 5 |
    5, 6, 3     # +---+---+---+---+---+---+---+---+---+
    5, 9, 1     # |   |   |   |   | 8 |   |   | 7 | 9 |
    6, 1, 7     # +---+---+---+---+---+---+---+---+---+
    6, 5, 2
    6, 9, 6
    7, 2, 6
    7, 7, 2
    7, 8, 8
    8, 4, 4
    8, 5, 1
    8, 6, 9
    8, 9, 5
    9, 5, 8
    9, 8, 7
    9, 9, 9
\end{lstlisting}

\pagebreak

\subsection{Loading Sudoku Puzzles}

\begin{lstlisting}[caption={Removing any useless characters}]
    lines = file.readlines()
    lines = map(lambda line: sub(r"#.*", "", line), lines)
    lines = map(lambda line: sub(r"\s+", "", line), lines)
    lines = enumerate(lines)
    lines = filter(lambda data: len(data[1]) > 0, lines)
\end{lstlisting}

\begin{lstlisting}[caption={Determining the size of the matrix}]
    try:
        index, line = next(lines)

        size = int(line)

        if size <= 0:
            raise ValueError

    except ValueError:
        raise ParseError(
            index, line, "is not a valid size specifier")
\end{lstlisting}

\begin{lstlisting}[caption={Performing some sanity checks corresponding to the size of the matrix}]
    _sqrt = sqrt(size)

    if _sqrt != int(_sqrt):
        raise ParseError(
            index, line, f"{size} is not a perfect square")
\end{lstlisting}

\pagebreak

\begin{lstlisting}[caption={Creating and populating a two-dimensional matrix while performing some sanity checks corresponding to each matrix entry}]
    matrix = [[None for _ in range(size)] for _ in range(size)]

    for index, line in lines:
        try:
            x, y, z = tuple(map(int, line.split(',')))

            if x < 0 or y < 0 or z < 0 or z > size:
                raise IndexError

            if matrix[x - 1][y - 1] is not None:
                raise ParseError(
                    index, line,
                    f"the cell has already been assigned")

            matrix[x - 1][y - 1] = z

        except IndexError:
            raise ParseError(
                index, line,
                f"is not a valid entry for a puzzle of size {size}")

        except ParseError as parse_error:
            raise parse_error

        except:
            raise ParseError(
                index, line, "Malformed entry")

    return matrix
\end{lstlisting}

\pagebreak

\subsection{Dumping Sudoku Puzzles}

\begin{lstlisting}[caption={Dumping a two-dimensional matrix corresponding to a Sudoku Puzzle to a file}]
    file.write(f"{len(matrix)}\n")

    for i in range(len(matrix)):
        for j in range(len(matrix)):
            if matrix[i][j] is not None:
                file.write(f"{i + 1}, {j + 1}, {matrix[i][j]}\n")
\end{lstlisting}

\end{document}


\par
\section{Data Structure}
\label{section:Pencil:dataStructure}
\par
\par
The {\tt Pencil} structure has the following fields.
\begin{itemize}
\item
{\tt int type} : type of matrix entries,
\begin{itemize}
\item {\tt SPOOLES\_REAL} for real entries
\item {\tt SPOOLES\_COMPLEX} for complex entries
\end{itemize}
\item
{\tt int symflag} : type of symmetry present in the matrices
\begin{itemize}
\item {\tt SPOOLES\_SYMMETRIC} for real or complex symmetric matrices
\item {\tt SPOOLES\_HERMITIAN} for complex Hermitian matrices
\item {\tt SPOOLES\_NONSYMMETRIC}
for real or complex nonsymmetric matrices
\end{itemize}
\item
{\tt InpMtx *inpmtxA} : pointer to the matrix object for $A$.
If {\tt inpmtxA} is {\tt NULL}, then $A$ is the identity matrix.
\item
{\tt InpMtx *inpmtxB} : pointer to the matrix object for $B$.
If {\tt inpmtxB} is {\tt NULL}, then $B$ is the identity matrix.
\item
{\tt double sigma[2]} : real or complex scalar shift value.
\end{itemize}


\documentclass{memoir}
\usepackage{linalg}

% \begin{figure}[ht]
%     \centering
%     \incfig{riemmans-theorem}
%     \caption{Riemmans theorem}
%     \label{fig:riemmans-theorem}
% \end{figure}

\begin{document}
\section{The Null Space and Range of the Dual of a Linear Map}
\begin{defn}[Annihilator]
	For $U \subset V$, the \textbf{annihilator} of $U $, denoted $U^{0}$, is defined by
	\begin{align*}
		U^{0} = \left\{\varphi\in V' \mid \varphi(u) = 0 \; \forall u \in U \right\} .
	\end{align*}
\end{defn}
\begin{exmp}
	Suppose $U$ is the subspace of $P(\R)$ consisting of all polynomial multiples of $x^2$. If $\varphi $ is the linear functional on $P(\R)$ defined by $\varphi(p) = p'(0)$, then $\varphi \in U^{0}.$
\end{exmp}
\begin{cor}
	Suppose $U \subset V$. Then $U^{0}$ is a subspace of $V'$.
\end{cor}
\begin{thm}[Dimension of Annihilator]
	Suppose $V$ is finite-dimensional and $U$ is a subspace of $V$. Then
	\begin{align*}
		\textrm{dim}U + \textrm{dim}U^0 = \textrm{dim}V.
	\end{align*}
\end{thm}
\begin{thm}[Null Space of $T'$ ]
	Suppose $V,W$ are finite-dimensional and $T \in \mathcal{L}(V,W)$. Then
	\begin{itemize}
		\item $\textrm{null}T' = ( \textrm{range}T)^{0}$
		\item $ \textrm{dim} \textrm{ null} (T') = \textrm{dim} \textrm{ null} (T) + \textrm{dim} (W) - \textrm{dim} (V)$
	\end{itemize}
\end{thm}
\begin{cor}
	Suppose $V,W$ are finite dimensional and $T \in \mathcal{L}(V,W)$. Then $T$ is surjective if and only if $T'$ is injective.
\end{cor}
\begin{thm}[Range of $T'$ ]
	Suppose $V,W$ are finite-dimensional and $T \in \mathcal{L}(V,W)$. Then
	\begin{itemize}
		\item $ \textrm{dim} \textrm{ range }T' = \textrm{dim} \textrm{ range }T $
		\item $\textrm{range }T' = ( \textrm{null} T)^0$
	\end{itemize}
\end{thm}
\begin{cor}
	Suppose $V,W$ are finite-dimensional and $T \in \mathcal{L}(V,W)$. Then $T$ is injective if and only if $T'$ is surjective.
\end{cor}

\subsection{Rank}
% Missing some definitions and theorems-- fill in later, Axler 3F
We will define row and column ranks initially, but keep in mind that this terminology will soon prove to be superfluous.
\begin{defn}[Row and Column Rank]
	Suppose \(A\) is an \(m\)-by-\(n\) matrix with entries in \(F\). The \textbf{row rank} of \(A\) is the dimension of the span of the rows of \(A\) in \(F^{1,n}\), and the \textbf{column rank} of \(A\) is the dimension of the span of the columns of \(A\) in \(F^{m,1}\).
\end{defn}
One can then prove that the column rank of \(\mathcal{M}(T)\) is equal to \(\textrm{dim}\textrm{Im}(T)\).

\begin{lemma}
Suppose $A \in F^{m,n}$. Then the row rank of $A$ equals the column rank of $A$.
\end{lemma}
This is what allows us to get rid of the superfluous terminology and create a simpler definition:
\begin{defn}[rank]
	The \textbf{rank} of a matrix \(A \in F^{m,n}\) is the column rank of \(A\). That is, the dimension of the span of the columns of \(A\) in \(F^{m,1}\), or the dimension of \(\textrm{Im}(A)\).
\end{defn}

\end{document}


\chapter{Compton Scattering}
There are two leading order diagrams for the process \HepProcess{\Pphoton \Pe \to \Pphoton \Pe}, shown in Figure \ref{fig:compton}. Note the similarity to electron-positron annihilation (rotate by $90\si{\degree}$).
\begin{figure}[h]
\centering
\begin{subfigure}[b]{0.3\textwidth}
\include{figures/compton_s}
\caption{s-channel\label{fig:compton_s}}
\end{subfigure}\hspace{40pt}
\begin{subfigure}[b]{0.3\textwidth}
\include{figures/compton_u}
\caption{u-channel\label{fig:compton_u}}
\end{subfigure}
\caption{Diagrams for the Compton scattering process \HepProcess{\Pphoton \Pe \to \Pphoton \Pe}.\label{fig:compton}}
\end{figure}

The total scattering probability is given by $\abs{\mathcal{M}}^2$ where $\mathcal{M} = \mathcal{M}_s + \mathcal{M}_u$, such that $\abs{\mathcal{M}}^2 = \abs{\mathcal{M}_s}^2 + \abs{\mathcal{M}_u}^2 + 2\operatorname{Re}(\mathcal{M}_s^*\mathcal{M}_u)$.
\section{s-channel}
Consider the s-channel diagram \ref{fig:compton_s} as a double scattering process. The scattering amplitude is given by
\begin{align}
i\mathcal{M}_s &= \int \int \phi^*(x_2) V(x_2) G_0(x_2;x_1) V(x_1) \phi(x_1) \, \dd[4]{x_1} \, \dd[4]{x_2} \\
&= \overline{u}(p^\prime)\,(-ie\gamma^\mu)\,\epsilon_\mu^*(k^\prime) \left(\frac{i(\fsl{p}+\fsl{k}+m)}{(p+k)^2-m^2} \right)(-ie\gamma^\nu)\,\epsilon_\nu(k)\, u(p)
\end{align}
where the second line can be obtained using Feynman rules.

Now in the massless limit where $m\rightarrow0$,
\begin{equation}
\mathcal{M}_s = \frac{-e^2}{s} \, \overline{u}(p^\prime) \, \gamma^\mu\epsilon_\mu^*(k^\prime) \, (\fsl{p} + \fsl{k})\, \gamma^\nu\epsilon_\nu(k) \, u(p) \label{eq:s_channel_amplitude}
\end{equation}
hence
\begin{align}
\abs{\mathcal{M}_s}^2 &= \frac{e^4}{s^2}\left[\left( \overline{u}(p^\prime) \, \gamma^\mu\epsilon_\mu^*(k^\prime) \, (\fsl{p} + \fsl{k})\, \gamma^\nu\epsilon_\nu(k) \, u(p)\right)\left( \overline{u}(p) \, \gamma^\rho\epsilon_\rho(k^\prime) \, (\fsl{p} + \fsl{k})\, \gamma^\sigma\epsilon_\sigma^*(k) \, u(p^\prime)\right) \right] \nonumber \\
&= \frac{e^4}{s^2}\left[ \epsilon_\rho(k^\prime)\,\epsilon_\mu^*(k^\prime)\,\epsilon_\nu(k)\,\epsilon_\sigma^*(k) \, \overline{u}(p^\prime)\,\gamma^\mu\,(\fsl{p}+\fsl{k})\,\gamma^\nu \, u(p) \, \overline{u}(p)\,\gamma^\rho\,(\fsl{p}+\fsl{k}) \, \gamma^\sigma \, u(p^\prime) \right]
\end{align}
where in the last line all the (Dirac scalar) polarisation vectors have been collected together. We now employ the completeness relation,
\begin{equation}
\sum \epsilon_\mu(p) \epsilon_\nu^*(p) = -g_{\mu\nu}
\end{equation}
where the sum is over all polarisations of the photon. Performing the sum over spins using the electron completeness relation \eqref{eq:completeness} ($\sum u\overline{u} = \fsl{p}+m$) and averaging over all initial spin states (divide by 4),
\begin{align}
\abs{\mathcal{M}_s}^2 &= \frac{e^4}{4s^2} \Tr \left[ g_{\rho\mu} \, g_{\nu\sigma} \, (\fsl{p^\prime}+m)\,\gamma^\mu\,(\fsl{p}+\fsl{k})\,\gamma^\nu \, (\fsl{p}+m) \,\gamma^\rho\,(\fsl{p}+\fsl{k}) \, \gamma^\sigma \right] \\
&= \frac{e^4}{4s^2}\Tr\left[(\fsl{p^\prime}+m)\,\gamma^\mu\,(\fsl{p}+\fsl{k})\,\gamma^\nu \, (\fsl{p}+m) \,\gamma_\mu\,(\fsl{p}+\fsl{k}) \, \gamma_\nu \right]\\
&= \frac{e^2}{s^2}\Tr[\fsl{p^\prime}\,(\fsl{p}+\fsl{k})\,\fsl{p}\,(\fsl{p}+\fsl{k})]
\end{align}
where to get the final line the electron mass terms have been neglected and the relation $\gamma^\mu\fsl{p}\gamma_\mu = -2\fsl{p}$ used. Now use the fact that $p\cdot p = m^2$ is small to neglect the terms containing $\fsl{p}^2$ or higher and use trace theorems to expand,
\begin{align}
\abs{\mathcal{M}_s}^2 &= \frac{e^2}{s^2}\Tr[\fsl{p^\prime}\,\fsl{k}\,\fsl{p}\,\fsl{k}] \\
&= \frac{4e^4}{s^2}\left[ (p^\prime \cdot k)(p \cdot k) - (p^\prime \cdot p)(k \cdot k) - (p^\prime \cdot k)(k \cdot p) \right] \\
&= \frac{4e^2}{s^2}\left[ 2(p^\prime \cdot k)(p \cdot k) \right]
\end{align}
where the last equation comes from the fact that $k \cdot k =0$ for the massless photon.

Now recall the Mandelstam variables,
\begin{equation*}
s = (k+p)^2 = (k^\prime + p^\prime)^2, \quad t = (k-k^\prime)^2 = (p-p^\prime)^2, \quad u = (k-p^\prime)^2 = (p-k^\prime)^2.
\end{equation*}
In the massless limit, we can approximate,
\begin{equation}
s = (k+p)^2 \approx 2k \cdot p, \quad u = (k-p^\prime)^2 \approx -2p^\prime \cdot k.
\end{equation}
Substituting into the result from above,
\begin{equation}
\abs{\mathcal{M}_s}^2 = 2e^4\frac{-u}{s}.
\end{equation}

\section{u-channel}
For the u-channel, the same steps yield the swapping of the $s$ and $u$ variables,
\begin{equation}
\abs{\mathcal{M}_u}^2 = 2e^4\frac{-s}{u}.
\end{equation}

\section{Interference term}
Now the remaining term is $2\operatorname{Re}(\mathcal{M}_s^*\mathcal{M}_u)$. Taking the Hermitian conjugate of \eqref{eq:s_channel_amplitude},
\begin{equation}
\mathcal{M}_s^* = -\frac{e^2}{s} \, \overline{u}(p) \, \gamma^\mu \, \epsilon_\mu(k^\prime) \, (\fsl{p}+\fsl{k}) \, \gamma^\nu \, \epsilon_\nu^*(k) \, u(p^\prime).
\end{equation}
For the u-channel matrix element, we simply swap $k \leftrightarrow -k^\prime$ so that $s \leftrightarrow u$,
\begin{equation}
\mathcal{M}_u = -\frac{e^2}{u} \, \overline{u}(p^\prime) \, \gamma^\rho \, \epsilon_\rho^*(-k) \, (\fsl{p} - \fsl{k^\prime}) \, \gamma^\sigma \, \epsilon_\sigma(-k^\prime) \, u(p).
\end{equation}
Multiplying these together,
\begin{align}
\mathcal{M}_s^*\mathcal{M}_u &= \frac{e^4}{su}\left[ \overline{u}(p) \, \gamma^\mu \, \epsilon_\mu(k^\prime) \, (\fsl{p}+\fsl{k}) \, \gamma^\nu \, \epsilon_\nu^*(k) \, u(p^\prime) \, \overline{u}(p^\prime) \, \gamma^\rho \, \epsilon_\rho^*(-k) \, (\fsl{p} - \fsl{k^\prime}) \, \gamma^\sigma \, \epsilon_\sigma(-k^\prime) \, u(p) \right] \nonumber \\
&= \frac{e^4}{su} \frac{1}{4} \Tr[g_{\mu\rho} \, g_{\nu\sigma} \, (\fsl{p}+m) \, \gamma^\mu \, (\fsl{p}+\fsl{k}) \, \gamma^\nu \, (\fsl{p^\prime} + m) \, \gamma^\rho \, (\fsl{p} - \fsl{k^\prime}) \, \gamma^\sigma] \nonumber \qquad \text{(sum-average and completeness relations)} \\
&= \frac{e^4}{su} \frac{1}{4} \Tr[(\fsl{p}+m)\,\gamma^\mu\,(\fsl{p}+\fsl{k}) \, \gamma^\nu \, (\fsl{p^\prime}+m) \, \gamma_\mu \, (\fsl{p}-\fsl{k^\prime})\gamma_\nu] \nonumber \qquad\text{(index gynmastics)}\\
&\approx \frac{e^4}{su} \frac{1}{4} \Tr[\fsl{p}\,\gamma^\mu\,(\fsl{p}+\fsl{k})\,\gamma^\nu\,\fsl{p^\prime}\,\gamma_\mu\,(\fsl{p}-\fsl{k^\prime})\,\gamma_\nu] \nonumber \qquad \text{(ignore $m$)} \\
&= \frac{e^4}{su} \frac{1}{4} \Tr[-2(\fsl{p}+\fsl{k}) \, \gamma^\mu \, \fsl{p} \, \fsl{p^\prime} \, \gamma_\mu \, (\fsl{p} - \fsl{k^\prime})] \nonumber \qquad \text{($\gamma_\nu \, \fsl{a} \, \fsl \, {b} \, \gamma^\nu = -2 \, \fsl{b} \, \fsl{a} \, \Rightarrow \, \gamma_\nu\,\fsl{a}\,\gamma^\mu\,\fsl{b}\,\gamma^\nu = -2\,\fsl{b}\,\gamma^\mu\,\fsl{a}$)}\\
&=\frac{-2e^4}{su} \Tr[(\fsl{p}+\fsl{k})(p \cdot p^\prime)(\fsl{p}-\fsl{k^\prime})] \nonumber \qquad \text{($\gamma^\mu \, \fsl{p} \, \fsl{p^\prime} \, \gamma_\mu = 4 p \cdot p^\prime$)} \\
&= \frac{-2e^4}{su} \, (p \cdot p^\prime) \, 4\left[(p+k)\cdot(p-k^\prime)\right] \nonumber \qquad \text{($\Tr[\fsl{a}\,\fsl{b}] = 4a \cdot b$)} \\
&= \frac{-8e^4}{su} \, (p \cdot p^\prime) \left( p \cdot p + k \cdot p - p \cdot k^\prime - k \cdot k^\prime \right)
\end{align}
Now use that
\begin{equation}
t \approx -2p \cdot p^\prime \approx -2k \cdot k^\prime, \quad s \approx 2p \cdot k, \quad u \approx -2p \cdot k^\prime
\end{equation}
and neglect the $p \cdot p = m^2$ term,
\begin{align}
\mathcal{M}_s^*\mathcal{M}_u &= \frac{-8e^4}{su} \frac{-t}{2} \left( \frac{s}{2}  + \frac{u}{2} + \frac{t}{2} \right) \nonumber \\
&= 2e^4 \frac{t}{su}(s+u+t)
\end{align}
Hence the interference contribution to the cross-section is
\begin{equation}
2\operatorname{Re}(\mathcal{M}_s^*\mathcal{M}_u) = 2e^4 \frac{2t}{su} (s+u+t).
\end{equation}

\section{Cross-section}
From \eqref{eq:madelstamSum} we know that $s+u+t = m^2 \approx 0$ for real  (i.e.~not virtual) photons. This means that, for a real photon scattering from an approximately massless electron, the s- and u-channel diagrams have no interference.

Hence the differential cross-section is given by
\begin{align}
\dv{\sigma}{\Omega} &= \frac{1}{64\pi^2 s} \, 2e^4 \left( \frac{-u}{s} + \frac{-s}{u} \right)\label{eq:comptonCrossSection}
\end{align}

Conversely, for a virtual photon $s+u+t=Q^2$ giving a differential cross-section
\begin{equation}
\dv{\sigma}{\Omega} = \frac{1}{64\pi^2 s} \, 2e^4 \left( \frac{-u}{2} + \frac{-s}{u} + \frac{2t}{su} Q^2 \right)
\end{equation}

\subsection{\HepProcess{\Pelectron\Ppositron\to\Pphoton\Pphoton} cross-section}
Note that rotating the diagrams in Figure \ref{fig:compton} by $90\si{\degree}$ yields all allowed diagrams for \HepProcess{\Pelectron\Ppositron\to\Pphoton\Pphoton}. Under this transformation we simply swap $s$ and $t$ in the scattering amplitude calculation, so the cross-section in the case real photons is
\begin{equation}
\dv{\sigma(\HepProcess{\Pelectron\Ppositron\to\Pphoton\Pphoton})}{\Omega} = \frac{1}{64\pi^2 s} 2e^4 \left( \frac{-u}{t} + \frac{-t}{u} \right).
\end{equation}

\subsection{Total cross-section when $m \rightarrow 0$, $s \rightarrow \infty$}
Taking large $s$, \eqref{eq:comptonCrossSection} becomes
\begin{equation}
\left. \dv{\sigma}{\Omega}\right|_{s\rightarrow\infty} = \frac{1}{64\pi^2}\frac{-2e^4}{u}.
\end{equation}
Substituting $u \approx -2p \cdot k^\prime$,
\begin{equation}
\left. \dv{\sigma}{\Omega}\right|_{s\rightarrow\infty} = \frac{1}{64\pi^2} \frac{e^4}{p\cdot k^\prime}\label{eq:compton_cross_section_limit}
\end{equation}

Now we choose to evaluate the cross-section in the centre-of-mass frame, where
\begin{equation*}
p = \mqty(E_e \\ \vec{p}), \quad k^\prime = \mqty(E_\gamma \\ \vec{k}^\prime).
\end{equation*}
Then
\begin{align}
p\cdot k^\prime &= E_e E_\gamma - \vec{p}\cdot\vec{k}^\prime \nonumber \\
&= E_e E_\gamma + \abs{\vec{p}}E_\gamma\cos\theta
\end{align}
where we have used that $\vec{p}\cdot\vec{k}^\prime = -\vec{p}\cdot\vec{p}^\prime = -\abs{\vec{p}}\abs{\vec{p}^\prime}\cos\theta = -\abs{\vec{p}}E_\gamma\cos\theta$. Now in the small mass case, expand $E_e$
\begin{equation}
E_e = \left( \abs{\vec{p}}^2 + m^2 \right)^\frac{1}{2} \approx \abs{\vec{p}}\left( 1 + \frac{m^2}{2\abs{\vec{p}}^2} \right),
\end{equation}
giving
\begin{align}
p\cdot k^\prime &= \abs{\vec{p}}\left( 1 + \frac{m^2}{2\abs{\vec{p}}^2} \right)E_\gamma + \abs{\vec{p}}E_\gamma\cos\theta \nonumber \\
&= \abs{\vec{p}}E_\gamma \left( 1 + \cos\theta + \frac{m^2}{2\abs{\vec{p}}^2} \right)
\end{align}
Now in the massless electron case, we have that $\abs{\vec{p}} = \sqrt{s}/2 = E_\gamma$. Therefore,
\begin{equation}
p\cdot k^\prime = \frac{s}{4} \left( 1 + \cos\theta + \frac{2m^2}{s} \right)
\end{equation}
up to first order in $m^2$. Substituting this result into \eqref{eq:compton_cross_section_limit}
\begin{align}
\left.\dv{\sigma}{\Omega}\right|_{s\rightarrow\infty} &= \frac{e^4}{64\pi^2}\frac{4}{s\left( 1 + \cos\theta + \frac{2m^2}{s} \right)} \\
&= \frac{\alpha^2}{s\left( 1 + \cos\theta + \frac{2m^2}{s} \right)}
\end{align}
using $\alpha = e^2/4\pi$. This is easily integrable,
\begin{align}
\sigma &= \frac{\alpha^2}{s} \int \frac{\dd{\Omega}}{1 + \cos\theta + \frac{2m^2}{s}} \nonumber \\
&= \frac{2\pi\alpha^2}{s}\int\limits_{-1}^{1}\frac{\dd{\cos\theta}}{1 + \cos\theta + \frac{2m^2}{s}}\nonumber \\
&= \frac{2\pi\alpha^2}{s} \ln\left[1 + \cos\theta + \frac{2m^2}{s}\right]_{-1}^{1} \nonumber\\
&= \frac{2\pi\alpha^2}{s} \ln\left[ \frac{2 + \frac{2m^2}{s}}{\frac{2m^2}{s}} \right]\nonumber\\
&= \frac{2\pi\alpha^2}{s}\ln\left[\frac{s}{m^2}+1\right]\nonumber\\
&\approx \frac{2\pi\alpha^2}{s}\ln\left(\frac{s}{m^2}\right).
\end{align}