\section{System model and problem statement}
\label{system-model}
Our model is defined by a set $\Delta=\{p_1, p_2,...,p_N\}$ of N processes, uniquely identified by an identifier $i$ known to all others. In the Byzantine fault model, it is assumed that there are at most $f < \floor{N/3}$ Byzantine nodes, which can exhibit arbitrary behavior.

Furthermore, the processes are connected by a network which can be represented by an undirected graph $G=(V, E)$. In this graph every vertex represents a process $p_i$, such that $p_i \in \Delta$, which means $V=\Delta$. The edges represent the communication links between nodes.
Processes $p_i, p_j \in \Delta$ have a direct communication link if there exists an edge $(v_i, v_j) \in E$, which they can use to directly communicate with each other. If there exists no such link, they will have to rely on other processes to relay their messages. We assume that these links are authenticated, i.e., messages delivered at $p_i \in \Delta$ via edge $(v_i, v_j) \in E$ are guaranteed to originate from $p_j$, and vice-versa. In addition to this, the links are reliable, i.e., messages will always arrive at $p_i$ if and only if $p_j$ sent them over edge $(v_i, v_j)$. However, there is no delivery time or delivery order guarantee, so a link can be synchronous or asynchronous. Graph $G$ is known to all processes, and so are the identifiers for every process. Furthermore, it is assumed the network is static, i.e., the network topology does not change, and one or more processes can broadcast messages simultaneously. The processes are used as the underlying layer for application code, which receives data from the process when it delivers a message to the application layer.

To send message data to others, processes can add information to the message header, which can be used to uniquely identify the message and add protocol-specific information.

A Byzantine Reliable Broadcast (BRB) protocol guarantees the following properties:\\
(i) \textbf{Validity}: If process $p_i \in \Delta$ broadcasts message $m$, then every correct process $p_j \in \Delta$ delivers $m$ at some point.\\
(ii) \textbf{No duplication}: A message $m$ broadcast by process $p_i \in \Delta$ is not delivered more than once by every correct process $p_j \in \Delta$.\\
(iii) \textbf{Integrity}: If process $p_j \in \Delta$ delivers message $m$ with sender $p_i$, process $p_i$ has broadcast $m$ in the past.\\
(iv) \textbf{Agreement}: If process $p_i \in \Delta$ delivers message $m$, then $m$ will eventually be delivered by every correct process $p_j \in \Delta$.

% \begin{itemize}
%     \item \textbf{Validity}: If process $p_i \in \Delta$ broadcasts message $m$, then every correct process $p_j \in \Delta$ delivers $m$ at some point.
%     \item \textbf{No duplication}: A message $m$ broadcast by process $p_i \in \Delta$ is not delivered more than once by every correct process $p_j \in \Delta$.
%     \item \textbf{Integrity}: If process $p_j \in \Delta$ delivers message $m$ with sender $p_i$, process $p_i$ has broadcast $m$ in the past.
%     \item \textbf{Agreement}: If process $p_i \in \Delta$ delivers message $m$, then $m$ will eventually be delivered by every correct process $p_j \in \Delta$.
% \end{itemize}

We will be introducing improvements to both Dolev~\cite{dolev}, Bracha~\cite{bracha}, and Bracha-Dolev~\cite{bracha-dolev}, which make different assumptions about the network $G$ and provide different guaranteed properties.
%\jd{?}

%\jd{why do you repeat the papers' assumptions here? Which assumptions do you make?}

Dolev assumes a network $G$ that is at least $2f+1$-connected.
%, i.e. there are at least $2f+1$ vertex-disjoint paths from every vertex $v_i \in V$ to every vertex $v_j \in (V - \{v_i\})$.
Furthermore, Dolev provides Reliable Communication (RC) which guarantees the same properties as BRB, except for the \textbf{Agreement} property. Bracha assumes a fully connected network $G$, i.e. for every pair $v_i,v_j \in V$ there exists an edge $(v_i, v_j) \in E$. Unlike Dolev, Bracha guarantees all BRB properties.

We make the same assumptions as the mentioned protocols while adding topology knowledge and static networks.

\subsection*{Reducing the number of messages}

While all mentioned protocols work well in their designed environments, there is naturally a substantial amount of redundant work when processes are unaware of the network topology.

% In the case of Dolev, for example, the network is being flooded with the same message to reach all processes over at least $f+1$ vertex-disjoint paths. However, if the network is overprovisioned, i.e. the network is $k$-connected where $k > 2f+1$, the message will take more paths than strictly necessary. In addition to this, processes will send the message to (almost) all neighbors leading to overlapping paths. Even though recent improvements have reduced the number of required messages
% %from $\mathcal{O}(n!)$ to nearly $\mathcal{O}(n)$~\cite{bonomi2019multihop}
%, it is possible this can still be reduced if processes know the network topology. The original paper describes a routed version that mitigates this problem. However, the actual creation of the routes is not discussed.

% For Bracha there are similar inefficiencies. For example, Bracha uses all nodes to come to an agreement, while only a subset of size $\ceil{\frac{N+f+1}{2}} + f$ is needed in the \textit{echo} phase and a smaller subset of size $3f+1$ for the final \textit{ready} phase.

% The optimizations can be combined and applied to Bracha-Dolev, also reducing the number of required messages for that protocol.

This paper aims to reduce the number of messages which are transmitted through the network for the three mentioned protocols. This reduces the network usage even further, assuming that all processes know the network topology. In addition to this, it might be possible to improve the delivery complexity of Dolev in the process by taking advantage of the fact that messages will traverse fixed paths. Furthermore, while the general process for handling known topologies for Dolev has been described in the original paper~\cite{dolev}, no actual implementation has been provided, which is something this paper will also do and is an additional contribution of this work.

\section{Background}
\label{background}
In this section, we will explain Dolev's and Bracha's protocols, and how they can be combined into Bracha-Dolev.

\subsection*{Dolev}
Dolev's protocol provides reliable communication when the network has authenticated links and is at least $2f+1$-connected.

When a message traverses the network, processes leverage the authenticated links to build a traversal path for each message. Said paths have two purposes: avoiding transmission loops and message verification.
The former is at play when processes relay messages to their neighbors; a message is forwarded to all neighbors, except to the transmitter and processes which are already present in the path. This is required to avoid messages circulating through the network indefinitely.
The paths are also used for verification; a message is delivered whenever it has been received over $f+1$ disjoint paths.

The basis for the correctness for Dolev's protocol lies in Menger's theorem~\cite{menger} which shows that there exist $2f+1$ disjoint paths between every pair of processes if a network is $2f+1$-connected, and the fact that at most $f$ of those paths can contain one or more Byzantine processes.
% This follows from the fact that a single process can only be in a single path, otherwise, the path would not be disjoint, so the worst-case scenario is that all $f$ Byzantine processes are spread over all the $2f+1$ disjoint paths, which leaves $f+1$ paths not containing a Byzantine process.

% There are multiple ways to verify that a message has traversed $f+1$ disjoint paths, but the problem is often reduced to a max flow problem or a minimum vertex cut problem~\cite{bonomi2019multihop}. \textbf{TODO: refs to set packing and hitting set?}
% We will elaborate on the first option. In this case, paths are modeled as a flow network where every edge has a capacity of one. The maximum flow through the network from source $p_j$ to sink $p_i$ is then equal to the number of disjoint paths. The max flow can be found with the Ford-Fulkerson algorithm~\cite{ford_fulkerson} for example.
%\jd{this only works with non-Byzantine processes} ---> Why??
Pseudocode for Dolev's protocol for a single message is provided in Algorithm~\ref{background:dolev} in Appendix~\ref{appendix-pseudocode}.

% \begin{algorithm}
%   \DontPrintSemicolon
%   \SetKwFunction{DInit}{Init}
%   \SetKwProg{Fn}{On event}{:}{}
%   \Fn{\DInit}{
%         delivered = False\;
%         paths = $\emptyset$\;
%   }

%   \SetKwFunction{DRecv}{Receive}
%   \Fn{\DRecv{$p_{recv}$, $m$, $path$}}{
%         $path$ = $path \cup \{p_{recv}\}$\;
%         \ForAll{$p_j \in neighbours - path$}{
%             transmit($p_j$, $m$, $path$)\;
%         }

%         paths.add($path$)\;

%         \uIf{paths contains $f+1$ node-disjoint paths to the origin \textbf{and} delivered = False}{
%             deliver($m$)\;
%             delivered = True\;
%         }
%   }

%   \SetKwFunction{DBrd}{Broadcast}
%   \Fn{\DBrd{$m$}}{
%         deliver($m$)\;
%         delivered = True\;

%         \ForAll{$p_j \in neighbours$}{
%             transmit($p_j$, $m$, $\emptyset$)\;
%         }
%   }
%  \caption{Dolev's Reliable Communication algorithm}
%  \label{background:dolev}
% \end{algorithm}

\subsection*{Bracha}
Unlike Dolev's protocol, Bracha's protocol requires a fully connected network while guaranteeing all four BRB properties, including the \textbf{Agreement} property.
%\jd{is it not the case for Dolev? You haven't explained which BRB property is not satisfied by Dolev}
The protocol has three phases: \textit{send}, \textit{echo}, and \textit{ready}.

When a process wants to broadcast a message it sends the payload in a \textit{send} message to all processes, including itself. When a process receives a \textit{send} messages, it responds by transmitting an \textit{echo} message to all processes with the corresponding content. Every process then waits for a minimum of $\ceil{\frac{N+f+1}{2}}$ \textit{echo} messages.
After this number has been reached, or $f+1$ \textit{ready} messages have been received, a process will transmit its own \textit{ready} message to all processes. Finally, a message will be delivered when $2f+1$ corresponding \textit{ready} messages have been received, as can be seen for a single message in Algorithm~\ref{background:bracha} in Appendix~\ref{appendix-pseudocode}.

\subsection*{Bracha-Dolev}
Dolev's and Bracha's protocol can be combined to achieve BRB guarantees in a multi-hop network, as described in \cite{bracha-dolev}.

This works by layering the two protocols, where Dolev's protocol forms the bottom layer. This means that every Bracha broadcast operation is replaced by a Dolev broadcast, and every Bracha receive operation by a Dolev deliver. %This process is shown in figure~\ref{background:bracha-dolev}.

By layering Bracha and Dolev, the latter emulates a fully connected network by enabling the former to reliably reach all processes. However, this means the message complexity of both protocols %, $\mathcal{O}(n^2)$ and $\mathcal{O}(n!)$ respectively,%
is essentially multiplied.

Instead of simply layering the two protocols, a cross-layer version~\cite{bonomi2021practical} can be used which allows for greater optimization.
% For comparison, this version can be seen in figure~\ref{background:bracha-dolev}.

% \begin{figure}
%     \centering
%     \begin{subfigure}{.2\textwidth}
%       \centering
%       \begin{tikzpicture}
%         	\begin{pgfonlayer}{nodelayer}
%         		\node [style=box] (0) at (-4.25, 6.25) {Application};
%         		\node [style=box] (1) at (-4.25, 5.25) {Bracha};
%         		\node [style=box] (2) at (-4.25, 4.25) {Dolev};
%         		\node [style=box] (3) at (-4.25, 3.25) {Network};
%         	\end{pgfonlayer}
%         	\begin{pgfonlayer}{edgelayer}
%         		\draw [style=directed edge double] (0) to (1);
%         		\draw [style=directed edge double] (1) to (2);
%         		\draw [style=directed edge double] (2) to (3);
%         	\end{pgfonlayer}
%         \end{tikzpicture}
%     %   \caption{Layering Bracha and Dolev forms the basis for Bracha-Dolev}
%     \end{subfigure}
%     \begin{subfigure}{.2\textwidth}
%       \centering
%       \begin{tikzpicture}
%         	\begin{pgfonlayer}{nodelayer}
%         		\node [style=box] (0) at (-4.25, 6.25) {Application};
%         		\node [style=box] (1) at (-4.25, 5.25) {Cross-layer BD};
%         		\node [style=box] (2) at (-4.25, 4.25) {Network};
%         % 		\node [style=none] (3) at (-4.25, 3.25) {};
%         	\end{pgfonlayer}
%         	\begin{pgfonlayer}{edgelayer}
%         		\draw [style=directed edge double] (0) to (1);
%         		\draw [style=directed edge double] (1) to (2);
%         	\end{pgfonlayer}
%         \end{tikzpicture}
%     %   \caption{A cross-layer combination can be used to improve performance~\cite{bonomi2021practical}}
%     \end{subfigure}
%     \caption{Bracha-Dolev can be implemented by simply layering the two protocols or by using a cross-layer protocol} %~\cite{bonomi2021practical}
%     \label{background:bracha-dolev}
% \end{figure}


% Copyright 2015 Charles Staats III
%
% Licensed under the Apache License, Version 2.0 (the "License");
% you may not use this file except in compliance with the License.
% You may obtain a copy of the License at
%
%     http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS,
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
% See the License for the specific language governing permissions and
% limitations under the License.

\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{lmodern}
\usepackage{asypictureB}
\usepackage{xcolor}
\usepackage{needspace}
\usepackage{listings}
\usepackage[justification=raggedright]{caption}
\usepackage{subcaption}
\usepackage{tikz}
\usetikzlibrary{spy}

%\setlength{\emergencystretch}{1em}
\tolerance=1000

% The maximum fraction of floats allowed at the top of a page:
\renewcommand{\topfraction}{0.9}

\lstset{language=C,
	breaklines=true,
	basicstyle=\ttfamily,
	breakatwhitespace=true,
	columns=flexible,
	keepspaces=true,
	numberblanklines=false,
	showstringspaces=false,
	commentstyle=\color{gray},
	backgroundcolor=\color{blue!10},
	frame=single,
	framerule=0pt,
	xleftmargin=3.01pt,
	xrightmargin=3.01pt,
	numberstyle=\footnotesize,
	showlines
	}
%
\newcommand{\mywidth}{}
\newif\ifinminipage
%
\newcommand{\begincodelisting}{%
\end{minipage}%
\inminipagetrue%
\hfill
\begin{minipage}[t]{\dimexpr\linewidth-\mywidth-7pt\relax}
\strut\par\vspace*{-\baselineskip}
\lstset{aboveskip=0pt}
}
%
\newcommand{\breakcodelisting}{%
\end{minipage}%
\inminipagefalse%
\begingroup%
\lstset{aboveskip=0pt}
}
%
\newenvironment*{asyexample}[1]%
{\par\bigskip%
\renewcommand{\mywidth}{#1}
\noindent
\begin{minipage}[t]{\mywidth}%
\mbox{}\\[-\baselineskip]}%
{\ifinminipage\end{minipage}\else\endgroup\fi\par\medskip}

\usepackage[hidelinks]{hyperref}

\title{The \texttt{smoothcontour3} module for Asymptote}
\author{Charles Staats III}
\date{\today}

\begin{document}
\maketitle
\begin{abstract}
The \lstinline!smoothcontour3! module uses an adaptive algorithm to draw implicitly defined surfaces
with smooth appearance. This documentation explains its use.
\end{abstract}
\tableofcontents
%
\section{Usage}
%
The API for this module consists of the single function \lstinline!implicitsurface()!:
\begin{lstlisting}
surface implicitsurface(real f(triple) = null,
                        real ff(real,real,real) = null,
                        triple a,
                        triple b,
                        int n = nmesh,
                        bool keyword overlapedges = false,
                        int keyword nx=n,
                        int keyword ny=n,
                        int keyword nz=n,
                        int keyword maxdepth = 8);
\end{lstlisting}
The function has three required\footnotemark{}
parameters: a function $f$ (of type either
\lstinline!real(triple)! or \lstinline!real(real,real,real)!) and two triples
\lstinline!a! and !b!. It returns a \lstinline!surface! that
approximates the zero locus of $f$ within the rectangular solid
with opposite corners \lstinline!a! and \lstinline!b!.
%
\footnotetext{The syntax seems to say that
\lstinline!f! and \lstinline!ff! are both optional parameters.
However, the function will throw a runtime error unless exactly one of
\lstinline!f!, \lstinline!ff! is set. This somewhat peculiar arrangement
was chosen so that the module works in all three of the following scenarios:
\begin{itemize}
\item The user wants to graph the zero set of a function
\lstinline!f(real,real,real)!.
\item The user wants to graph the zero set of a function
\lstinline!f(triple)!.
\item The user has overloaded a function name \lstinline!f!,
defining both \lstinline!f(triple)! and \lstinline!f(real,real,real)!.
\end{itemize}
While the first two cases could be covered by overloading
\lstinline!implicitsurface!, that would cause an ambiguous call in the third
case.}

\needspace{3\baselineskip}
Here are the explanations of the optional parameters:
\begin{itemize}
\item \lstinline!int n!---the number of initial segments in each of the
$x$, $y$, $z$ directions. Defaults to \lstinline!nmesh!, which is usually 10.
\item \lstinline!bool overlapedges!---if \lstinline!true!, the patches of the surface are slightly enlarged to compensate for an artifact in which the viewer can see through the boundary between patches. (Some of this may actually be a result of edges not lining up perfectly, but I'm fairly sure a lot of it arises purely as a rendering artifact.) Defaults to \lstinline!false!. Keyword required---this parameter
can only be used in key-value format.
\item \lstinline!int nx!---overrides \lstinline!n! in the $x$ direction.
Keyword required.
\item \lstinline!int ny!---overrides \lstinline!n! in the $y$ direction.
Keyword required.
\item \lstinline!int nz!---overrides \lstinline!n! in the $z$ direction.
Keyword required.
\item \lstinline!int maxdepth!---the maximum depth to which the adaptive algorithm will subdivide in an effort to find patches that closely approximate the true surface.  Keyword required.
\end{itemize}

\section{When should I use this module?}
The algorithm is designed to deal with a differentiable function with a smooth zero
locus. Simple point singularities in the zero locus are usually not a problem,
but complex singularities (e.g. two tangent spheres) or one-dimensional
singularities will take a long time to compute with no guarantee of a
good result.

On one occasion I have seen it cope with a continuous, piecewise differentiable function, although it took a while to compute. Whatever you do, \emph{do not}
pass it a piecewise constant function---it will take forever and return nothing good.

\section{Examples}
\subsection{Genus three surface}
\begin{figure}
\centering
\begin{minipage}[b]{\dimexpr\textwidth-9cm}
\subcaption{\lstinline!overlapedges=true!}
\end{minipage}
\begin{minipage}{8.8cm}%
% A tikzpicture is used here for spacing consistency with the next
% subfigure, which uses the tikz spy library.
\begin{tikzpicture}
\draw (0,0) node [above right] {%
\begin{asypicture}{name=genus_three}
settings.outformat = "png";
settings.render = 4;
size(8cm);

import smoothcontour3;

// Erdos lemniscate of order n:
real erdos(pair z, int n) { return abs(z^n-1)^2 - 1; }

real h = 0.12;

// Erdos lemniscate of order 3:
real lemn3(real x, real y) { return erdos((x,y), 3); }

// "Inflate" the order-3 lemniscate into a smooth surface:
real f(real x, real y, real z) {
  return lemn3(x,y)^2 + (16*abs((x,y))^4 + 1) * (z^2 - h^2);
}

draw(implicitsurface(f, (-3,-3,-3), (3,3,3),
                     overlapedges=true),
     surfacepen=material(diffusepen=gray(0.5),
			                   emissivepen=gray(0.3),
			                   specularpen=gray(0.1)) );
\end{asypicture}
};
\end{tikzpicture}
\xdef\genusthreelisting{\asylistingfile}%
\end{minipage}
\par
\begin{minipage}[b]{\dimexpr\textwidth-9cm}
\subcaption{without setting \lstinline!overlapedges!}
\label{subfigure:nooverlapedges}
\end{minipage}
\begin{minipage}{8.8cm}
\begin{tikzpicture}
  [spy using outlines = {circle,
                         magnification=4,
                         size=2cm,
                         connect spies}]
\draw (0,0) node[above right]{%
\begin{asypicture}{name=genus_three_nooverlapedges}
settings.outformat = "png";
settings.render = 4;
size(8cm);

import smoothcontour3;

// Erdos lemniscate of order n:
real erdos(pair z, int n) { return abs(z^n-1)^2 - 1; }

real h = 0.12;

// Erdos lemniscate of order 3:
real lemn3(real x, real y) { return erdos((x,y), 3); }

// "Inflate" the order-3 lemniscate into a smooth surface:
real f(real x, real y, real z) {
  return lemn3(x,y)^2 + (16*abs((x,y))^4 + 1) * (z^2 - h^2);
}

draw(implicitsurface(f, (-3,-3,-3), (3,3,3)),
     surfacepen=material(diffusepen=gray(0.5),
			                   emissivepen=gray(0.3),
			                   specularpen=gray(0.1)) );
\end{asypicture}
};
\spy on (2.0,1.0) in node[below] at (5.0, 0.9);
\end{tikzpicture}
\end{minipage}
\caption{A smooth surface of genus three.}\label{figure:genus3}
\end{figure}
The first example is code for a smooth surface of genus three;
the output is Figure~\ref{figure:genus3}. Note that because of
the adaptive nature of the algorithm, we can get a good result
without having to set \lstinline!nx!, \lstinline!ny!, or
\lstinline!nz! manually.
\lstinputlisting[firstline=5]{\genusthreelisting}
Also shown is the result obtained by omitting the parameter \lstinline!overlapedges=true!, with the rendering artifacts emphasized. The figure
on top is not wholly devoid of such artifacts, but they are fewer and
less prominent.
Increasing \lstinline!settings.render! can also reduce the appearance
of such artifacts.

\subsection{Cropped sphere}
Next, we see how the module can be used to produce a cropping effect: If
a surface can be described as the zero locus of a smooth function, then
the plotted zero locus is automatically cropped to the specified
rectangular solid.
\begin{asyexample}{3.5cm}
\begin{asypicture}{name=croppedsphere}
settings.outformat="png";
settings.render=8;
size(@mywidth,0);
import smoothcontour3;
currentprojection = orthographic(20,1,3);

real f(triple w) { return w.x^2 + w.y^2 + w.z^2 - 1; }

draw(implicitsurface(f, (-0.98,-0.98,-0.98), (0.98,0.98,0.98),
             n=2, overlapedges=true),
     surfacepen=olive);
\end{asypicture}
\begincodelisting
\lstinputlisting[firstline=5,lastline=12]{\asylistingfile}
\breakcodelisting
\lstinputlisting[firstline=13]{\asylistingfile}
\end{asyexample}
\noindent The parameter \lstinline!n=2! is specified to produce a more efficient
surface. This version of the cropped sphere has ``only'' 408 B\'ezier patches.
This is in part because the algorithm decides that \lstinline!n=2! is actually
to small and subdivides everything, so actually \lstinline!n=2! and
\lstinline!n=4! give identical results.
But by comparison, the default \lstinline!n=10!
gives 1152 patches, with no apparent change in the quality of the rendering.

Another thing to note here is that the algorithm would not ``notice'' the
need to crop the sphere if the vertices were not positioned exactly right:
in this case, \lstinline!n=2! produces better results than \lstinline!n=3!.
We do get good results for \lstinline!n=5!. If you change the cropping to
$0.99$ instead of $0.98$, then \lstinline!n=2! still works great, but
\lstinline!n=9! is the first \emph{odd} value of \lstinline!n! at which
the cropping shows up.

\subsection{Genus two surface}
\begin{figure}
\noindent
\begin{subfigure}{0.48\linewidth}
\centering
\begin{asypicture}{name=genus_two_with_gaps}
settings.outformat="png";
settings.render=8;
size(0.95*@the@linewidth, 0);
import smoothcontour3;
import contour;
currentprojection=perspective((18,20,10));

real tuberadius = 0.69;

// Convert to cylindrical coordinates to draw
// a circle revolved about the z axis.
real toruscontour(real x, real y, real z) {
  real r = sqrt(x^2 + y^2);
  return (r-2)^2 + z^2 - tuberadius^2;
}

// Take the union of the two tangent tori (by taking
// the product of the functions defining them). Then
// add (or subtract) a bit of noise to smooth things
// out.
real f(real x, real y, real z) {
  real f1 = toruscontour(x - 2 - tuberadius, y, z);
  real f2 = toruscontour(x + 2 + tuberadius, y, z);
  return f1 * f2 - 0.1;
}

// The noisy function extends a bit farther than the union of
// the two tori, so include a bit of extra space in the box.
triple max = (2*(2+tuberadius), 2+tuberadius, tuberadius) + (0.1, 0.1, 0.1);

// Draw the implicit surface.
draw(implicitsurface(f, -max, max, overlapedges=true),
     surfacepen=white);
\end{asypicture}
\xdef\genustwowithgaps{\asylistingfile}%
\caption{default parameters}\label{subfigure:genus2withgaps}
\end{subfigure}
\hfill
\begin{subfigure}{0.48\linewidth}
\centering
\begin{asypicture}{name=genus_two}
settings.outformat="png";
settings.render=8;
size(0.95*@the@linewidth, 0);
import smoothcontour3;
import contour;
currentprojection=perspective((18,20,10));

real tuberadius = 0.69;

// Convert to cylindrical coordinates to draw
// a circle revolved about the z axis.
real toruscontour(real x, real y, real z) {
  real r = sqrt(x^2 + y^2);
  return (r-2)^2 + z^2 - tuberadius^2;
}

// Take the union of the two tangent tori (by taking
// the product of the functions defining them). Then
// add (or subtract) a bit of noise to smooth things
// out.
real f(real x, real y, real z) {
  real f1 = toruscontour(x - 2 - tuberadius, y, z);
  real f2 = toruscontour(x + 2 + tuberadius, y, z);
  return f1 * f2 - 0.1;
}

// The noisy function extends a bit farther than the union of
// the two tori, so include a bit of extra space in the box.
triple max = (2*(2+tuberadius), 2+tuberadius, tuberadius) + (0.1, 0.1, 0.1);

// Draw the implicit surface.
draw(implicitsurface(f, -max, max, overlapedges=true,
                     nx=20, nz=5),
     surfacepen=white);
\end{asypicture}
\xdef\genustwo{\asylistingfile}
\caption{\lstinline!nx=20!, \lstinline!nz=5!}\label{subfigure:genus2}
\end{subfigure}
\bigskip\par\noindent
\begin{minipage}{\textwidth}
\centering
\begin{asypicture}{name=genus_two_with_grid}
settings.outformat="png";
settings.render=8;
size(10cm,0);
import smoothcontour3;
import contour;
currentprojection=perspective((18,20,10));

real tuberadius = 0.69;

// Convert to cylindrical coordinates to draw
// a circle revolved about the z axis.
real toruscontour(real x, real y, real z) {
  real r = sqrt(x^2 + y^2);
  return (r-2)^2 + z^2 - tuberadius^2;
}

// Take the union of the two tangent tori (by taking
// the product of the functions defining them). Then
// add (or subtract) a bit of noise to smooth things
// out.
real f(real x, real y, real z) {
  real f1 = toruscontour(x - 2 - tuberadius, y, z);
  real f2 = toruscontour(x + 2 + tuberadius, y, z);
  return f1 * f2 - 0.1;
}

// The noisy function extends a bit farther than the union of
// the two tori, so include a bit of extra space in the box.
triple max = (2*(2+tuberadius), 2+tuberadius, tuberadius) + (0.1, 0.1, 0.1);
triple min = -max;

// Draw the implicit surface.
draw(implicitsurface(f, min, max, overlapedges=true,
                     nx=20, nz=5),
     surfacepen=material(green+0.8blue+0.1red,
			 emissivepen=0.2blue+0.2red,
			 specularpen=gray(0.1)));

/****************** WARNING **************************/
// The following code (for drawing gridlines) is highly
// memory-intensive. On my computer, it used over
// 1.7 GB of RAM.
/*****************************************************/

// To draw the gridlines, we need to compute the implicit curves
// in the planes defined by fixing one of the three variables.
typedef real function2(pair);
function2 xfixed(real x) {
  return new real(pair p) { return f(x, p.x, p.y); };
}
function2 yfixed(real y) {
  return new real(pair p) { return f(p.x, y, p.y); };
}
function2 zfixed(real z) {
  return new real(pair p) { return f(p.x, p.y, z); };
}

// These planes are used to lift a path to a path3.
typedef triple plane(pair);
plane liftx(real x) {
  return new triple(pair p) { return (x, p.x, p.y); };
}
plane lifty(real y) {
  return new triple(pair p) { return (p.x, y, p.y); };
}
plane liftz(real z) {
  return new triple(pair p) { return (p.x, p.y, z); };
}

pen gridpen = 0.8 red + 0.3 blue + linewidth(0.3pt);

int nx=20, ny=10, nz=5;

for (int i = 0; i <= nx; ++i) {
  real x = (i/nx) * (max.x - min.x) + min.x;
  guide[] wholepath = contour(xfixed(x), (min.y,min.z),
                              (max.y,max.z),
                              new real[]{0}, 200)[0];
  plane currentplane = liftx(x);
  for (guide pathpiece : wholepath) {
    draw(path3(pathpiece, currentplane), gridpen);
  }
}

for (int j = 0; j <= ny; ++j) {
  real y = (j/ny) * (max.y - min.y) + min.y;
  guide[] wholepath = contour(yfixed(y), (min.x,min.z),
                              (max.x,max.z),
                              new real[]{0}, 200)[0];
  plane currentplane = lifty(y);
  for (guide pathpiece : wholepath) {
    draw(path3(pathpiece, currentplane), gridpen);
  }
}

for (int k = 0; k <= nz; ++k) {
  real z = (k/nz) * (max.z - min.z) + min.z;
  guide[] wholepath = contour(zfixed(z), (min.x,min.y),
                              (max.x,max.y),
                              new real[]{0}, 200)[0];
  plane currentplane = liftz(z);
  for (guide pathpiece : wholepath) {
    draw(path3(pathpiece, currentplane), gridpen);
  }
}
\end{asypicture}
\xdef\genustwowithgridlines{\asylistingfile}%
\subcaption{gridlines drawn using
the \lstinline!contour! module}\label{subfigure:genus2withgridlines}
\end{minipage}
\caption{A surface of genus two.}
\end{figure}
Here's an attempt to draw a surface of genus two
(with the result shown in Figure \ref{subfigure:genus2withgaps}):
\lstinputlisting[firstline=5]{\genustwowithgaps}
The algorithm, as you can see, is not always smart enough to identify
all the things it should draw. In this case, the gaps can be eliminated
by changing the $10 \times 10 \times 10$ grid used for initial computations
to a $20 \times 10 \times 5$ grid. so that the individual cells are closer
to being cubes. To do this, replace the final line by
\lstinputlisting[firstline=36]{\genustwo}
One more variant, shown in Figure \ref{subfigure:genus2withgridlines}, involves
using the pre-existing \lstinline!contour! module to compute gridlines.
Note that this is highly memory-intensive.
\begin{lstlisting}[escapechar=*]

*\smash{\vdots}*
\end{lstlisting}
\vspace*{-\baselineskip}
\lstinputlisting[firstline=7,lastline=7]{\genustwowithgridlines}
\vspace*{-\baselineskip}
\begin{lstlisting}[escapechar=*]

*\smash{\vdots}*
\end{lstlisting}
\vspace*{-\baselineskip}
\lstinputlisting[firstline=33]{\genustwowithgridlines}

\section{Troubleshooting}
In most graphing algorithms in Asymptote, the user specifies the number of
subdivisions, and many artifacts can be corrected by increasing this number.
For drawing smooth implicit surfaces, that approach proved impractical.
Instead, the algorithm
attempts to detect artifacts itself, and subdivides into
smaller cells when it does.
This works surprisingly well, but not perfectly.

Most of the artifacts below can be mitigated by increasing
the number of subdivisions using the parameter \lstinline!n!.
However, simply increasing this parameter
should be regarded as a ``nuclear option.'' In addition to
significantly increasing the computational resources required,
it actually makes the first and most common artifact \emph{worse}.
%
\subsection{Random missing pixels}
Perhaps the most common artifact to show up when using this package
is that scattered pixels will randomly show up the wrong color; see
Figure \ref{subfigure:nooverlapedges}, p.~\pageref{subfigure:nooverlapedges}.
These pixels are actually showing up at the edges between patches, where the renderer
believes (usually mistakenly) that there is a gap it can ``see through.'' There are
several ways to mitigate the problem; they can be used together except for the last.
\begin{enumerate}
\item Set \lstinline!overlapedges = true! in the call to
\lstinline!implicitsurface()!. This
option artificially inflates the individual patches by $1\%$ in an effort to convince the
renderer that all the gaps between edges are filled. It usually helps a lot.
\item Increase \lstinline!settings.render!. As a general rule, asking the renderer to work
harder will mitigate the random missing pixels.
\item Decrease the parameters
\lstinline!n! and/or \lstinline!nx!, \lstinline!ny!, \lstinline!nz!. If you can decrease the number
of patches (which is not guaranteed), then you correspondingly get fewer edges to produce problems.
Also, if the patches are larger (since fewer), then \lstinline!overlapedges=true! becomes more effective
since a $1\%$ increase in the size of the patches produces a bigger increase in absolute terms.
\item When drawing the surface, use a \lstinline!meshpen!. This is not recommended---the mesh you
get will not be one you want to see---but it will fill the gaps. Do \emph{not} use this with
\lstinline!overlapedges=true!.
\end{enumerate}
%
\subsection{Gaps in the surface}\label{subsection:gapsinsurface}
Sometimes, the algorithm will fail to notice a piece of the surface that should
be drawn, leaving a gap as in Figure \ref{subfigure:genus2withgaps}.
\begin{enumerate}
\item \textbf{Jiggle it a little.} Change small things to see if you can remove
the coincidence that stumped the algorithm. For instance, add or subtract a
small amount from the bounds, or change \lstinline!n! by one. In
the case of Figure \ref{subfigure:genus2withgaps}, subtracting $0.05$ from the
upper $z$ bound (and adding $0.05$ to the lower $z$ bound) is sufficient to
eliminate the problem.
\item \textbf{Judiciously change \lstinline!nx!, \lstinline!ny!, \lstinline!nz!.}
For instance, if $z$ can be described as a function of $(x,y)$, try setting
\lstinline!nz=1!.
In the case of Figure \ref{subfigure:genus2withgaps}, the gaps are long
in the $x$ direction, so you might try doubling \lstinline!nx! to $20$ from
the default of $10$. Since the gaps have little or no extent in the
$z$ direction, and since the figure itself is not high in the $z$ direction,
you might simultaneously decrease \lstinline!nz! from $10$ to $5$. In this
way, you have solved the issue (producing
Figure~\ref{subfigure:genus2}). Furthermore, since
$20 \cdot 10 \cdot 5 = 10 \cdot 10 \cdot 10$, you have not actually
increased the computation time.
\item \textbf{Significantly increase \lstinline!n!.} Theoretically, if
the surface is smooth, \lstinline!n! is sufficiently large, and a small random
amount of random noise is added to the bounds, errors of this sort should occur
only with probability zero.\footnote{This is true for a suitably high level of
abstraction. At a lower level of abstraction, a computer is a finite-state
machine, so there's no such thing as ``possible but with probability zero.''}
\end{enumerate}
\subsection{Important features drawn wrong}
For instance, a ``cropped sphere'' might fail to be cropped, or a very tiny
sphere might go missing from the diagram. Or bad things could happen if the
surface is supposed to have multiple ``sheets'' in close proximity.

The advice here is the same as in \ref{subsection:gapsinsurface};
however, this time the problem
is too vaguely worded for me to offer any ``probability zero'' guarantees.
\subsection{Strange smoothness artifacts}
\begin{figure}
\centering
\begin{asypicture}{name=conewithspyglass}
settings.outformat="pdf";
settings.render=16;
settings.prc=false;
size(10cm);

import smoothcontour3;

currentprojection=perspective(
camera=(2.02192872015484,4.99392113582712,3.92208163966281),
up=(-0.000455904165055687,-0.001742164203227,0.00236915601403234),
target=(0.028573554295769,0.0228009523917252,-0.117030854731037));

real f(real x, real y, real z) {
  return x^2 + y^2 - z^2;
}

draw(implicitsurface(f, (-1,-1,-1), (1,1,1), overlapedges=true),
     surfacepen=material(gray(0.5)));

//dot(0.4*(1,0,-1));

frame theframe = currentpicture.fit();
currentpicture = new picture;
unitsize(1cm);

pair thepoint = (0.2,-1.8);
path thecircle = circle(c=thepoint, r=0.3);
path bigcircle = scale(4) * thecircle;
add(scale(4) * theframe);
layer();
clip(bigcircle);
transform t = shift(5,0) * shift(-3 thepoint);
currentpicture = t * currentpicture;
bigcircle = t * bigcircle;
pair center2 = t * scale(4) * thepoint;
add(theframe);
layer();
draw(bigcircle, blue);
draw(thecircle, blue);
draw(lastcut(firstcut(thepoint -- center2, thecircle).after, bigcircle).before,
     blue);
\end{asypicture}
\caption{The graph of $x^2 + y^2 - z^2 = 0$ over the domain
$[-1,1]^3$. Note the minor wrinkle.}\label{figure:conewithspyglass}
\end{figure}
Sometimes strange artifacts show up, such as the wrinkle in
Figure~\ref{figure:conewithspyglass}. To deal with this, you can try the same
\ref{subsection:gapsinsurface}, but with absolutely no assurance of
success. Another thing you can try is to play with the coloring, lighting,
and point of view to make the artifact less obvious. (In truth, it required
some playing with coloring, lighting, and point of view to make the artifact
visible in Figure~\ref{figure:conewithspyglass}.)

One particular piece of advice here: a strong \lstinline!specularpen! can
highlight any such artifacts if they actually exist. So if you are trying to
de-emphasize an artifact, decreasing the \lstinline!specularpen! and increasing
the other pens in the \lstinline!material! may be wise.

\section{License}
The following license applies to the both the \lstinline!smoothcontour3! module and this
documentation:
\begin{verbatim}
Copyright 2015 Charles Staats III

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
\end{verbatim}
\end{document}


\documentclass[11pt]{article}
\usepackage[margin=1in]{geometry}
\usepackage{amsmath}
\usepackage{dsfont}
\usepackage{graphicx}
\usepackage[utf8]{inputenc}
\usepackage{xcolor}
% \usepackage{soul}
% \usepackage{authblk}
\usepackage{verbatim}
% \usepackage[legalpaper, margin=2in]{geometry}
\parindent0pt
\parskip 1.5ex plus 1ex minus .5ex

\definecolor{Blue}{rgb}{0,0,1}
\definecolor{Red}{rgb}{1,0,0}
\definecolor{Green}{rgb}{0,1,0}
\definecolor{Bronze}{rgb}{0.8,0.5,0.2}
\definecolor{Violet}{rgb}{0.54,0.17,0.89}

\newcommand{\TODO}[1]{{\textcolor{Violet}{TODO: #1}}}
\newcommand{\YC}[1]{{\textcolor{Bronze}{#1}}}
\newcommand{\SG}[1]{{\textcolor{Blue}{#1}}}

\DeclareMathOperator{\Tr}{Tr}
\newcommand{\Ell}{\mathcal{L}}
\newcommand{\R}{\mathds{R}}
\newcommand{\N}{\mathds{N}}
\newcommand{\C}{\mathds{C}}
\newcommand{\bfa}{\boldsymbol{\alpha}}
\newcommand{\bs}[1]{{\boldsymbol{#1}}}

\newcommand\blfootnote[1]{%
  \begingroup
  \renewcommand\thefootnote{}\footnote{#1}%
  \addtocounter{footnote}{-1}%
  \endgroup
}

\title{Quandary: Optimal Control for Open Quantum Systems}
\author{Stefanie G{\"u}nther\thanks{Center for Applied Scientific Computing, Lawrence Livermore National Laboratory, Livermore, CA, USA.} \and N. Anders Petersson$^*$}
% \author{Stefanie G{\"u}nther \and N. Anders Petersson}
\date{last updated \today}

\begin{document}
\maketitle

\tableofcontents

\section{Introduction}
Quandary numerically simulates and optimizes the time evolution of open quantum systems. The
underlying dynamics are modeled by Lindblad's master equation, a linear ordinary differential
equation (ODE) describing quantum systems interacting with the environment. Quandary solves this ODE
numerically by applying a time-stepping integration scheme and applies a gradient-based optimization
scheme to determine optimal control pulses that drive the quantum system to a desired target state.
Two optimization objectives are considered: Unitary gate optimization that finds controls to
realize a logical quantum gate, and pure-state preparation that aims to drive the quantum system to a desired pure target state, such as the ground state of zero energy level.

Quandary is designed to solve optimal control problems in larger open quantum systems targeting modern high performance computing (HPC) platforms. Quandary utilizes distributed memory computations using the message passing paradigm that enables scalability to large number of compute cores. Implemented in C++, Quandary is portable and its object-oriented implementation allows developers to easily extend the predefined setup to suit their particular simulation and optimization requirements. For example, customized gates for Hamiltonian simulations can easily be added to supplement Quandary’s predefined gate set.

This document outlines the mathematical background and underlying equations, and summarizes their
implementation in Quandary. For installation instructions and execution, please take a
look at the \texttt{README.md} file. Further, the configuration file \texttt{config\_template.cfg} is filled with comments that should help users and developers to set up new simulation and optimization runs, and match the input options to the equations found in this document.
A full documentation is under development. In the meantime, don't
hesitate to direct any questions to guenther5@llnl.gov.
We also refer to our publications \cite{guenther2021quandary, guenther2021quantum}.

\section{Model equation}
Quandary models composite quantum systems consisting of $Q$ subsystems, with $n_k$ energy levels for the
$k$-th subsystem, $k=0,\dots,Q-1$. The time-evolution of the open quantum system is modeled by Lindblad's master equation:
\begin{align}\label{mastereq}
  \dot \rho(t) = &-i(H(t)\rho(t) - \rho(t)H(t)) + \Ell(\rho(t))
\end{align}
for the density matrix $\rho(t)\in \C^{N\times N}$ with dimension $N :=
\prod_{k=0}^{Q-1} n_k$, where $H(t)$ denotes the Hamiltonian describing the system and control and $\Ell(\rho(t))$ denotes the Lindbladian operator that models system-environment interactions as specified below.

The Hamiltonian $H(t)$ is decomposed into a time-independent system part $H_d$ and a time-varying control part ($H_c(t)$) that employs the external control fields: $H(t) = H_d + H_c(t)$. The system Hamiltonian is modeld as
\begin{align}
  H_d &:= \sum_{k=0}^{Q-1} \left(\omega_k a_k^{\dagger}a_k- \frac{\xi_k}{2} a_k^{\dagger}a_k^{\dagger}a_k a_k  + \sum_{l> k} \left(  J_{kl} \left( a_k^\dagger a_l + a_k a_l^\dagger \right) -\xi_{kl} a_{k}^{\dagger}a_{k}   a_{l}^{\dagger} a_{l} \right)\right)
\end{align}
where $\omega_k>0$ denotes the $0 \rightarrow 1$ transition frequency and $\xi_k>0$ is the self-Kerr coefficient of subsystem $k$, and the cross resonance coefficients are $J_{kl}>0$ (``dipole-dipole interaction'') and $\xi_{kl}>0$ (``zz-coupling''). Here,
$a_k\in \C^{N\times N}$ denotes the lowering operator acting on subsystem $k$, which is defined as
\begin{align}
  \begin{array}{rl}
  a_0 &:= a^{(n_0)} \otimes I_{n_1} \otimes \dots \otimes
  I_{n_{Q-1}}\\
  a_1 &:= I_{n_0} \otimes a^{(n_1)} \otimes \dots \otimes
  I_{n_{Q-1}}\\
  \vdots \, & \\
  a_{Q-1} &:= I_{n_0} \otimes I_{n_1} \otimes \dots \otimes
  a^{(n_{Q-1})}\\
  \end{array}
  \quad \text{with}\quad
 a^{(n_k)} := \begin{pmatrix}
   0 & 1 &          &         &    \\
     & 0 & \sqrt{2} &         &     \\
     &   & \ddots   & \ddots  &    \\
     &   &          &         & \sqrt{n_k-1}  \\
     &   &          &         & 0
 \end{pmatrix} \in \R^{n_k \times n_k}
\end{align}
where $I_{n_k} \in \R^{n_k \times n_k}$ is the identity matrix.

The action of external control fields on the quantum system is modelled through the control Hamiltonian
\begin{align}
  H_c(t) &:= \sum_{k=0}^{Q-1} f^k(\vec{\alpha}^k,t) \left(a_k + a_k^\dagger \right)
\end{align}
where $f^k(\vec{\alpha}^k,t)$ are real-valued, time-dependent control functions that are parameterized by real-valued parameters $\vec{\alpha}^k\in \R^d$, which are to be determined through optimization.

The Lindbladian operator $\Ell(\rho(t))$ is assumed to be of the form
\begin{align}
  \Ell(\rho(t)) = \sum_{k=0}^{Q-1} \sum_{l=1}^2 \Ell_{lk} \rho(t)
  \Ell_{lk}^{\dagger} - \frac 1 2 \left( \Ell_{lk}^{\dagger}\Ell_{lk}
  \rho(t) + \rho(t)\Ell_{lk}^{\dagger} \Ell_{lk}\right).
\end{align}
where the collapse operators $\Ell_{lk}$ model decay and dephasing processes in the subsystem with $\Ell_{1k} = \frac{1}{\sqrt{T_1^k}} a_k$ (``T1'' -- decay) and $\Ell_{2k} = \frac{1}{\sqrt{T_2^k}} a_k^{\dagger}a_k$ (``T2'' -- dephasing) for each subsystem $k$. The constants $T_l^k>0$ correspond to the half-life of process $l$ on subsystem $k$. Typical T1 decay time is between $10-100$ microseconds (us). T2 dephasing time is typically about half of T1 decay time.
% Decay processes typically behave like $\exp(-t/{T_1})$.

\subsection{Rotational frame approximation}
Quandary uses the rotating wave approximation in order to slow down the time scales in the solution of Lindblad's master equations. To that end, the user can specify the rotation frequencies $\omega_k^r$ for each oscillator. Under the rotating frame wave approximation, the Hamiltonians are transformed to
\begin{align}
  \tilde H_d(t) &:= \sum_{k=0}^{Q-1} \left(\omega_k - \omega_k^{r}\right)a_k^{\dagger}a_k- \frac{\xi_k}{2}
  a_k^{\dagger}a_k^{\dagger}a_k a_k
   - \sum_{l> k} \xi_{kl} a_{k}^{\dagger}a_{k}   a_{l}^{\dagger} a_{l} \notag \\
   & + \sum_{k=0}^{Q-1}\sum_{l>k} J_{kl} \left(\cos(\eta_{kl}t) \left(a_k^\dagger a_l + a_k a_l^\dagger\right) + i\sin(\eta_{kl}t)\left(a_k^\dagger a_l - a_k a_l^\dagger\right) \right) \label{eq:Hd_rotating} \\
   %
   \tilde H_c(t) &:= \sum_{k=0}^{Q-1} \left( p^k(\vec{\alpha}^k,t) (a_k +
   a_k^{\dagger}) + i q^k(\vec{\alpha}^k,t)(a_k - a_k^{\dagger})
   \right)  \label{eq:Hc_rotating}
\end{align}
where $\eta_{kl} := \omega_k^{r} - \omega_l^{r}$ are the differences in rotational frequencies between subsystems.

Note that the eigenvalues of the rotating frame Hamiltonian become significantly smaller in magnitude by choosing $\omega_k^r \approx \omega_k$ (so that the first term with $a_k^\dagger a_k$ drops out). This slows down the time variation of the state evolution, hence bigger time-step sizes can be chosen when solving the master equation numerically. We remark that the rotating wave approximation ignores terms in the control Hamiltonian that oscillate with frequencies $\pm 2\omega_k^r$. Below, we drop the tildes on $\tilde H_d$ and $\tilde H_c$ and use the rotating frame definition of the Hamiltonians to model the system evolution in time.

Using the rotating wave approximatino, the real-valued laboratory frame control functions are written as
\begin{align}
  f^k(\vec{\alpha}^k,t) = 2\mbox{Re}\left(d^k(\vec{\alpha}^k,t)e^{i\omega_k^r t}\right), \quad d^k(\vec{\alpha}^k,t) = p^k(\vec{\alpha}^k,t) + i q^k(\vec{\alpha}^k,t)
\end{align}
where the rotational frequencies $\omega_k^r$ act as carrier waves to the rotating-frame control functions $d^k(\vec{\alpha}^k, t)$.

\subsection{Control pulses} \label{subsec:controlpulses}
The time-dependent rotating-frame control functions $d^k(\vec{\alpha}^k,t)$ are parameterized using $N_s$ piecewise quadratic B-spline basis functions $B_s(t)$ acting as envelope for $N_f^k$ carrier waves:
\begin{align}
  d^k(\vec{\alpha}^k,t) = \sum_{f=1}^{N_f^k} \sum_{s=1}^{N_s} \alpha_{s,f}^k B_s(t) e^{i\Omega_k^ft}, \quad \alpha_{s,f}^k = \alpha_{s,f}^{k(1)} + i \alpha_{s,f}^{k(2)} \in \C
\end{align}
The amplitudes $\alpha_{s,f}^{k(1)}, \alpha_{s,f}^{k(2)} \in \R$ are the control
parameters (\textit{design} variables) that Quandary can optimize in order to realize a
desired system behavior, giving a total number of $2N_s\sum_kN_f^k$ real-valued optimization variables. Further, $\Omega_k^f \in \R$ denote the carrier wave frequencies in the rotating frame which can be choosen to trigger certain system frequencies. The corresponding Lab-frame carrier frequencies become $\omega_k^r + \Omega_k^f$. Those frequencies can be chosen to match the transition frequencies in the lab-frame system Hamiltonian. For example, when $\xi_{kl} << \xi_k$, the transition frequencies satisfy $\omega_k - n\xi_k$. Thus by choosing $\Omega_k^f = \omega_k-\omega_k^r - n \xi_k$, one triggers transition between energy levels $n$ and $n+1$ in subsystem $k$.

Using trigonometric identities, the real and imaginary part of the rotating-frame control $d^k(\vec{\alpha}^k,t) = p^k(\vec{\alpha}^k,t) + iq^k(\vec{\alpha}^k,t)$ are given by
\begin{align}
  p^k(\vec{\alpha}^k,t) &= \sum_{f=1}^{N_f^k} \sum_{s=1}^{N_s} B_s(t)
  \left(\alpha^{k
  (1)}_{s,f} \cos(\Omega_f^k t) - \alpha^{k (2)}_{lsf} \sin(\Omega_f^k t)
  \right) \\
  q^k(\vec{\alpha}^k,t) &= \sum_{f=1}^{N_f^k} \sum_{s=1}^{N_s} B_s(t)\left( \alpha^{k
  (1)}_{s,f} \sin(\Omega_f^k t) + \alpha^{k (2)}_{s,f} \cos(\Omega_f^k t)
  \right)
\end{align}
Those relate to the Lab-frame control $f^k(\vec{\alpha}^k,t)$ through
\begin{align}
  f^k(t) &=  2\sum_{f=1}^{N_f^k} \sum_{s=1}^{N_s} B_s(t) \left(\alpha_{s,f}^{k(1)} \cos((\omega_k^{r} + \Omega_f^k) t) - \alpha_{s,f}^{k(2)}\sin((\omega_k^{r} + \Omega_f^k) t) \right) \\
         &= 2 p^k(\vec{\alpha}^k, t) \cos(\omega_k^{r} t) - 2 q^k(\vec{\alpha}^k,
         t)\sin(\omega_k^{r} t) \\
         &= 2\mbox{Re}\left( d^k(\vec{\alpha}^k,t)e^{i\omega_k^r t} \right)
\end{align}

\subsubsection{Storage of the control parameters}

The control parameters $\bs{\alpha}$ are stored in the Quandary code in the following order: List oscillators first $(\vec{\alpha}^0, \dots, \vec{\alpha}^{Q-1})$, then for each $\vec{\alpha}^k \in
\R^{2N_sN_f^k}$, iterate over $N_s$ splines: $\vec{\alpha}^k =
(\alpha^k_1,\dots, \alpha^k_{N_s})$ with $\alpha^k_s \in \R^{2N_f^k}$, then each
$\alpha^k_s$ iterates over carrier waves and for each carrier wave lists
the real and imaginary components: $\alpha^k_s = \alpha^{k(1)}_{s,1}, \alpha^{k(2)}_{s,1},
\dots \alpha^{k(1)}_{s,N_f^k}, \alpha^{k(2)}_{s,N_f^k}$. Hence there are a total of $2N_s\sum_k N_f^k$ real-valued optimization parameters, which are stored in the following order:
  \begin{align}
    \boldsymbol{\alpha} &:= \left( \vec{\alpha}^0, \dots, \vec{\alpha}^{Q-1} \right), \in
    \mathds{R}^{2N_s\sum_k N_f^k} \quad \text{where}\\
    \vec{\alpha}^k &= \left( \alpha_{1,1}^k,\dots, \alpha_{1,N_f^k}^k, \dots,
    \alpha_{N_s,1}^{k}, \dots, \alpha_{N_s,N_f^k}^k \right), \quad \text{with} \quad
    \alpha_{s,f}^k = \left(\alpha_{s,f}^{k(1)}, \alpha_{s,f}^{k(2)} \right) \in
    \R^2,
  \end{align}
  iterating over $Q$ subsystems first, then $N_s$ splines, then $N_f^k$ carrier wave
  frequencies. To access an element $\alpha_{s,f}^{k(i)}$, $i=0,1$, from storage $\bfa$:
  \begin{align}
    \alpha_{s,f}^{k(i)} = \bfa[ \left(\sum_{j=0}^{k-1} 2N_sN_f^j\right) + s*2N_f^k + f*2 + i ],
  \end{align}

  When executing Quandary, the control parameter $\boldsymbol{\alpha}$ can be either specified (e.g. a constant pulse, a pi-pulse, or pulses whose parameters are read from a given file), or can be optimized for (Section \ref{sec:optim}).

  In order to guarantee that the optimizer yields control pulses that are
  bounded with $|p^k(t)| \leq c^k_{max}$, $|q^k(t)| \leq c^k_{max}$ for given bounds $c^k_{max}$ for each
  subsystem $k=0,\dots, Q-1$, box constraints are implemented as:
   \begin{align}
     | \alpha_{s,f}^{k(1)}| \leq \frac{c^k_{max}}{N_f^k} \quad \text{and} \quad |
     \alpha_{s,f}^{k(2)} | \leq \frac{c^k_{max}}{N_f^k}.
   \end{align}

\section{The Optimal Control Problem} \label{sec:optim}
In the most general form, Quandary can solve the following optimization problem
\begin{align}
  \min_{\boldsymbol{\alpha}} \frac{1}{n_{init}} \sum_{i=1}^{n_{init}} \beta_i J(\rho^{target}_i, \rho_i(T)) + \gamma_1 \int_0^T P\left(\rho_i(t)\right) \, \mathrm{d} t + \gamma_2 \| \bfa \|^2_2
\end{align}
where $\rho_i(T)$ solves Lindblad's master equation \eqref{mastereq} in the rotating frame with initial condition $\rho_i(0)$, as specified below.
The first term minimizes (a weighted average of) an objective function $J$ that quantifies the discrepancy between a desired target state $\rho^{target}_i$ and the realized state $\rho_i(T)$ at final time $T$ driven by the current control $\boldsymbol{\alpha}$.
The second term serves as a penalty that can be added with $\gamma_1 \geq 0$ in oder to achieve a desired behavior of the quantum system over the entire time-domain $0\leq t\leq T$, see below.
The third term is a Tikhonov regularization that can be added with parameter $\gamma_2\geq 0$ in order to regularize the optimization problem (stabilize optimization convergence) by favoring solutions with small norm.

\subsection{Objective function}
The following choices for $J$ are implemented
\begin{align}
 J_{Frobenius} &= \frac 12 \| \rho^{target} - \rho(T)||^2_F \\
 J_{Hilbert-Schmidt} &= 1 - \frac 1w \mbox{Tr}\left((\rho^{target})^\dagger\rho(T)\right), \quad \text{where} \quad w=\mbox{Tr}(\rho(0)^2)\\
 J_{Measure} &= \mbox{Tr} \left( N_m \rho(T) \right), \label{eq:Jmeasure}
\end{align}
where $N_m\in \R^{N\times N}$ is a diagonal matrix with diagonal elements $\lambda_k = |k-m|\, \forall k=0,\dots, N-1$ for a given integer $m$ with $0\leq m < N$. The first two measures are mostly common for gate optimization, $J_{measure}$ is useful when considering pure-state optimization, see below. In Quandary, the objective function can be specified with the configuration option \texttt{optim\_objective}.

\subsection{Optimization targets}
Here we describe the target states $\rho^{target}$ that are realized in Quandary. Two cases are considered: State preparation, where the target state is the same for all initial conditions, and gate optimization, where the target state is a unitary transformation of the initial condition. The type of the optimization target can be specified in the configuration option \texttt{optim\_target}.

\subsubsection{Pure target states}\label{sec:statepreparation}
State preparation aims to drive the system from either one specific or from any arbitrary initial state to a common desired (fixed) target state. Quandary can optimize towards \textit{pure} target states of the form
\begin{align}
 \rho^{target} = \boldsymbol{e}_m \boldsymbol{e}_m^\dagger, \quad \text{for}\quad m\in \N_0\quad \text{with}\quad 0\leq m < N
\end{align}
where $\boldsymbol{e}_m$ denotes the $m$-th unit vector in $\R^N$. We note that considering pure states of that specific form ($\boldsymbol{e}_m\boldsymbol{e}_m^\dagger$) is not a restriction, because any other pure target state can be transformed to this representation using a unitary change of coordinates (compare the Appendix in \cite{guenther2021quantum} for a more detailed description). For $m=0$, the target state represents the ground state of the system under consideration, which has important applications for quantum reset as well as quantum error correction.

Depending on the choice for the initial conditions, optimization towards a pure target state can be used to realize either a simple state-to-state transfer (choosing one specific initial condition, $n_{init}=1$), or to realize the more complex task of state preparation that drives \textit{any} initial state to a common pure target state, and then $n_{init} > 1$. In the latter case, typically a full basis of initial conditions needs to be considered during the optimization ($n_{init}=N^2$). However, it is shown in \cite{guenther2021quantum}, that if one chooses the objective function $J_{measure}$ with corresponding measurement operator $N_m$ (see eq. \eqref{eq:Jmeasure}), one can reduce the number of initial conditions to only \textit{one} being an ensemble of all basis states, and hence $n_{init}=1$ independent of the system dimension $N$. Compare \cite{guenther2021quantum} for details, and Section \ref{sec:ensemblestate}

Note that the above $m$ refers to the $|m\rangle$-th state of the entire system under consideration with dimension $N$, which can be a composite of $Q$ subsystems.
In Quandary this pure target state is specified by defining the desired pure target for \textit{each} of the subsystems individally. For a composite system of $Q$ subsystems with $n_k$ levels each, a composite target pure state is specified by a list of integers $m_k$ with $0\leq m_k < n_k$ representing the pure target state in each subsystem $k$. The composite pure target is state is then
\begin{align}
  \rho(0) = \Psi \Psi^\dagger, \quad \text{with} \quad \Psi &= |m_0 m_1 m_2 \dots m_{Q-1} \rangle \\
    \text{aka} \quad  \Psi &= \boldsymbol{e}_{m_0}\otimes  \boldsymbol{e}_{m_1}\otimes  \dots \otimes\boldsymbol{e}_{m_{Q-1}}
\end{align}
with unit vectors $\boldsymbol{e}_{m_k} \in \R^{n_k}$. The composite-system index $m$ is then computed inside Quandary,from
\begin{align}
  m = m_0 \frac{N}{n_0} + m_1 \frac{N}{n_0n_1} + m_2 \frac{N}{n_0n_1n_2} + \dots + m_{Q-1}
\end{align}

For example, assume one considers a composite system consisting of a qubit modelled with $n_0=2$ levels coupled to a readout cavity modelled with $n_1=10$ levels, and one wants to drive the qubit to the $|1\rangle$ state and the cavity to the ground state. The target pure state input for Quandary is hence $m_0=1, m_1=0$ (i.e. the $|10\rangle$ state in the composite system), which corresonds $m=1\cdot10+0 = 10$ in the composite system of dimension $N=2\cdot 10 = 20$.

\subsubsection{Read a target state from file}
A specific target state $\rho^{target}$ can also be read from file. This can be useful when considering non-pure target states. File format: The vectorized density matrix (columnwise vectorization), one number per line, first list all real parts, then list all imaginary parts (hence $2N^2$ lines with one number each).

\subsubsection{Gate optimization}

Quandary can be used to realize logical gate operations. In that case, the target state is not fixed across the initial states, but instead is a unitary transformation of each initial condition. Let $V\in \C^{N\times N}$ be a unitary matrix presenting a logical operation, the goal is to drive any initial state $\rho(0)$ to the unitary transformation $\rho^{target} = V\rho(0)V^{\dagger}$.
Target gates that are currently implemented are
\begin{align}
  V_{X} := \begin{bmatrix} 0 & 1 \\ 1 & 0  \end{bmatrix} \quad
  V_{Y} := \begin{bmatrix} 0 & -i \\ i & 0 \end{bmatrix} \quad
  V_{Z} := \begin{bmatrix} 1 & 0 \\ 0 & -1 \end{bmatrix} \quad
  V_{Hadamard} := \frac{1}{\sqrt{2}}
           \begin{bmatrix} 1 & 1 \\ 1 & -1 \end{bmatrix} \\
  V_{CNOT} := \begin{bmatrix} 1  & 0 & 0 & 0 \\
                               0  & 1 & 0 & 0 \\
                               0  & 0 & 0 & 1 \\
                               0  & 0 & 1 & 0 \\
                \end{bmatrix} \quad
  V_{SWAP} := \begin{bmatrix}
    1 & 0 & 0 & 0 \\
    0 & 0 & 1 & 0 \\
    0 & 1 & 0 & 0 \\
    0 & 0 & 0 & 1 \\
  \end{bmatrix}
\end{align}
Further, a generalization of the SWAP and the CNOT gate for general $Q$ subsystems is available: The SWAP-0Q gate swaps the state of the first and the last qubit, while leaving all other oscillators in their respective initial state, and the CQNOT gate performs a NOT operation on the last qubit if all other qubits are in the one-state.
New, user-defined gates can be easily added to the code by extending the \texttt{Gate} class in the corresponding \texttt{.cpp} and \texttt{.hpp} files.

For gate optimization, the first two objective function $J_{Frobenius}$ and $J_{Hilbert-Schmidt}$ are appropriate. Since \textit{any} initial quantum state should be transformed by the control pulses, again typically a basis for the initial states should be considered ($n_{init}=N^2$). However, it has been shown in \cite{goerz2014optimal} that it is enough to optimize with only three specific initial states ($n_{init} = 3$), independent of the Hilbert space dimension $N$. Those three states are set up in such a way that they can distinguish between any two unitaries in that Hilbert space. The three initial states are readily available in Quandary, see Section \ref{subsec:initcond}. Note that when optimizing with only those three initial states, it turns out that the choice of the weights $\beta_i$ that weight the contribution from each initial state in the overall objective function strongly influences the optimization convergence. For faster convergence, it is often benefitial to emphasize on the first of the three initial conditions ($\rho_1(0)$ in Section \ref{subsec:threeinitcond}), hence choosing $\beta_1$ (much) bigger than $\beta_2$ and $\beta_3$ (e.g. $\beta = \{20,1,1\}$ often works better than $\beta = \{1,1,1\}$, try it yourself). We refer to \cite{goerz2014optimal} for details.

\subsubsection{Essential and non-essential levels}
It is often useful, to model the quantum system with more energy levels than the number of levels that the target gate is defined on. For example when optimizing for a SWAP gate on two qubits, with $V_{SWAP}\in\C^{4\times 4}$, one might want to model each qubit with more than two energy levels in order to (a) model the (infinite dimensional) system with more accuracy by including more levels and (b) allow the system to transition through higher energy levels in order to achieve the target at final time $T$. In that case, the \textit{essential} levels denote the levels that the target gate is defined on.
To this end, Quandary provides the option to specify the number of essential energy levels $n_k^e$ in addition to the number of energy levels $n_k$, where $n_k^e \leq n_k$ for each subsystem $k$. The quantum dynamics are then modelled with (more) energy levels with $N=\prod_k n_k$ and $\rho(t)\in \C^{N\times N}$, while the gate is defined in the essential level dimensions only: $V\in C^{N_e \times N_e}, N_e=\prod_k n_k^e$. In the example above, $n^e_0=n^e_1=2$ and hence $V_{SWAP}\in \C^{4\times 4}$, but one can choose the number of energy levels $n_0$ and $n_1$ to be bigger than $2$ to when modelling the system dynamics.

To compute the objective function at final time T, the essential-dimensional gate is projected upwards to the full dimensions $\tilde V \in N\times N$ by inserting identiy blocks for rows/columns that correspond to a non-essential level of either of the subsystems. Hence, a realization of the gate $\tilde V$ will not alter the occupation of higher (non-essential) energy level compared to their initial occupation at $t=0$.

% When only the three states as initial conditions are considered (see below), those three initial states will be spanned in the full dimensional system. On the other hand, when the basis of initial states is considered, or their diagonals only, those initial states will be spanned only in the essential level dimensions and zero rows and columns will be inserted for all non-essential levels. Hence, the gate will be realized for any initial state that is spanned in the essential level dimensions, and occupations of non-essential levels at $T=0$ are avoided.

\subsection{Initial conditions}\label{subsec:initcond}
Quandary offers various choices to set the initial conditions for simulation and optimization in order to accomodate for different simulation and optimization needs. Below is a list of available options for the initial states (configuration option \texttt{initialcondition}).

\subsubsection{Pure-state initialization}
One can choose to simulate and optimize for only one specific pure initial state (then $n_{init} = 1$). The initial density matrix is then composed of Kronecker products of pure states for each of the subsystems. E.g. for a bipartite system with $n_1
\otimes n_2$ levels, one can propagate any initial pure state
\begin{align}
  \rho(0)  = |m_1\rangle \langle m_1| \otimes |m_2\rangle \langle m_2| \quad \text{for} \quad m_1 \in \{0,\dots, n_1-1\}, m_2\in \{0,\dots, n_2-1\}
\end{align}
Note that, again, in this notation $|m_1\rangle = \boldsymbol{e}_{m_1} \in \R^{n_1}$. The configuration input takes a list of the integers $m_k$ for each subsystem.

\subsubsection{Basis of density matrices}
To span any possible initial state, an entire basis of states can be used as initial conditions, and then $n_{init} = N^2$. Quandary implements the basis states as defined in \cite{guenther2021quantum}:
\begin{align}\label{eq:basismats}
B^{kj} := \frac 12 \left(\bs{e}_k\bs{e}_k^\dagger + \bs{e}_j\bs{e}_j^\dagger\right) +  \begin{cases}
          0 & \text{if} \, k=j \\
        \frac 12 \left( \bs{e}_k\bs{e}_j^\dagger  + \bs{e}_j\bs{e}_k^\dagger \right) & \text{if} \, k<j \\
        \frac i2 \left( \bs{e}_j\bs{e}_k^\dagger  - \bs{e}_k\bs{e}_j^\dagger \right) & \text{if} \, k>j
      \end{cases}
\end{align}
for all $k,j\in\{0,\dots, N-1\}$.
These density matrices represent $N^2$ pure states that span the space of all quantum states in this Hilberspace.

In order to uniquely identify the different initial conditions in the Quandary code and in the output files, a
unique index $i \in \{0,\dots, N^2-1\}$ is assigned to each basis state with
\begin{align*}
  B^i := B^{k(i), j(i)}, \quad \text{with} \quad k(i) := i \,\mbox{mod}\, N,
  \quad \text{and} \quad j(i) := \left\lfloor \frac{i}{N} \right\rfloor
\end{align*}
(columnwise vectorization of a matrix of matrices $B^{kj}$).

\subsubsection{Diagonal basis matrices (aka all pure states)}
One can choose to propagate only those basis elements that correspond to pure states of the form $\boldsymbol{e}_k\boldsymbol{e}_k^\dagger$, i.e. propagating only $B^{kk}$ for $k=0,\dots, N-1$, and then $n_{init}=N$.

\subsubsection{Ensemble state for pure-state optimization}\label{sec:ensemblestate}
For pure-state optimization using the objective function $J_{measure}$ \eqref{eq:Jmeasure}, one can use the ensemble state
\begin{align}\label{eq:ensemblestate}
  \rho_s(0) = \frac{1}{N^2}\sum_{i,j=0}^{N-1} B^{kj}
\end{align}
as the only initial condition for optimization or simulation ($\Rightarrow n_{init}=1$). Since Lindblad's master equation is linear in the initial condition, and $J_{measure}$ is linear in the final state, propagating this single initial state yields the same target value as if one propagates all basis states spanning that space and averages their measure at final time $T$ (compare \cite{guenther2021quantum}). To specify the ensemble state in Quandary for composite quantum systems with multiple subsystems, on can provide a list of integer ID's that determine in which of the subsystems the ensemble state should be spanned. Other subsystems will be initialized in the ground state.

To be precise: the user specifies a list of consecutive ID's $<k_0>, \dots, <k_m>$ with $0 \leq k_j \leq Q-1$ and $k_{j+1} = k_j+1$, the ensemble state $\rho_s(0)$ will be spanned in the dimension given by those subsystems, $N_s = \prod_{j=0}^{m} n_{k_j}$ and $\rho_s(0) \in \C^{N_s\times N_s}$ with basis matrices $B^{kj}$ spanned in $\C^{N_s\times N_s}$. The initial state that Quandary propagates is then given by
\begin{align}
  \rho(0) = \bs{e}_0\bs{e}_0^\dagger \otimes \underbrace{\rho_s(0)}_{\in \C^{N_s\times N_s}} \otimes \, \bs{e}_0 \bs{e}_0^\dagger
\end{align}
where the first $\bs{e}_0$ (before the kronecker product) is the first unit vector in $\R^{\prod_{k=0}^{k_0-1}}$ (i.e. ground state in all preceding subsystems), and the second $\bs{e}_0$ (behind the kronecker products) is the first unit vector in the dimension of all subsequent systems, $\R^{\prod_{k=k_m+1}^{Q-1}}$.

\subsubsection{Three initial states for gate optimization}\label{subsec:threeinitcond}
When considering gate optimization, it has been shown in \cite{goerz2014optimal} that it is enough to consider only three specific initial states during optimization ($n_{init}=3$), independent of the Hilber space dimension. Those three initial states are given by
\begin{align}
    \rho(0)_1 &= \sum_{i=0}^{N-1} \frac{2(N-i+1)}{N(N+1)} \bs{e}_i\bs{e}_i^\dagger \\
    \rho(0)_2 &= \sum_{ij=0}^{N-1} \frac{1}{N} \bs{e}_i\bs{e}_j^\dagger\\
    \rho(0)_3 &= \frac{1}{N} I_N
\end{align}
where $I_N\in R^{N\times N}$ is the identity matrix. They are readily implemented in Quandary. Note that it is important to choose the weights $\beta_i, i=1,2,3$ in the objective function appropriately to achieve fast convergence.

\subsubsection{$N+1$ initial states for gate optimization}
The three initial states from above do not suffice to estimate an average fidelity of the realized gate (compare \cite{goerz2014optimal}). Instead, it is suggested in that same paper to choose $N+1$ initial states to compute an average fidelity. Those $N+1$ initial states consist of the $N$ diagonal states $B^{kk}$ in the Hilbertspace of dimension $N$, as well as the totally rotated state $\rho(0)_2$ from above. Quandary offers the choice to simulate (or optimize) using those initial states, then $n_{init} = N+1$.

\subsubsection{Reading an initial state from file}
A specific initial state $\rho(0)$ can also be read from file ($\Rightarrow n_{init}=1$). Format: one column being the vectorized density matrix (vectorization is columnwise), first all real parts, then all imaginary parts (i.e. number of lines is $2N^2$, with one value per line).

This option is useful for example if one wants to propagate a specific \textit{non-pure} initial state. In that case, one first has to generate a datafile storing that state (e.g. by simulating a system and storing the output), which can then be read in as initial condition.

\subsection{Penalty term}
The penalty term can be added with parameter $\gamma_1 \geq 0$ in order to achieve a desired behavior at earlier times other than the final time $T$.
In the standard setting, the following penalty term is currently implemented (easily extendable for developers in the \texttt{TimeStepper} class.):
\begin{align}
 \gamma_1 \int_0^T P(\rho(t)) \mathrm{d}t = \gamma_1\int_0^T w(t) J\left(\rho(t)\right) \, \mathrm{d}t, \quad \text{where} \quad w(t) =
  \frac{1}{a} e^{ -\left(\frac{t-T}{a} \right)^2},
\end{align}
for a penalty parameter $0 < a \leq 1$. Note, that as $a\to 0$, the weighting function $w(t)$ converges to the Dirac delta distribution with peak at final time $T$, hence reducing $a$ leads to more emphasis on the final time $T$ while larger $a$ penalize non-zero energy states at earlier times $t\leq T$. Both $\gamma_1$ as well as $a$ can be tuned in the configuration options.

Be aware the adding an integral penalty term requires to evaluate the objective $J$ at \textit{each} time-step within the time-domain $[0,T]$, instead of only the last one. This can be quite expensive, in particular when considering gate optimization, and should hence be turned of if possible (choosing $\gamma_1=0$ will skip the extra computation!).

\textit{For gate optimization with (non-)essential levels:} When the essential levels for gate optimization are smaller than the number of modelled levels ($n_k^e < n_k$ for at least one $k$), an additional term is added as a penalty in order to prevent leakage into higher levels that are not modelled by the finite-dimensional approximation to the infinite dimensional Hamiltonian. In that case, the occupation of all guard levels (i.e. the last modelled energy levels of each of the subsystems) is penalized:
\begin{align}
  \gamma_1 \int_0^T \sum_{r} \| \rho(t)_{rr} \|^2_2 \, \mathrm{d}t
\end{align}
where $\rho(t)_{rr}$ denotes the diagonal element at $(r,r)$ inside $\rho(t)\in \C^{N\times N}$ and $r$ iterates over all indices that correspond to a guard level (last non-essential energy level) of at least one of the subsystems.

\subsection{Fidelity}
As a measure of optimization success, Quandary reports on the fidelity computed from
\begin{align}
  F = \frac{1}{n_{init}} \sum_{i=1}^{n_{init}} \mbox{Tr}\left(\left(\rho^{target}_i\right)^\dagger\rho_i(T) \right).
\end{align}
Note that this fidelity is averaged over the chosen initial conditions, so the user should be careful how to interpret this number. E.g. if one optimizes for a logical gate while choosing the three initial condition as in Section \ref{subsec:threeinitcond}, the fidelity that is reported during optimization will be averaged over those three initial states, which is not sufficient to estimate the actual average fidelity over the entire space of potential intial states. It is advised to recompute the average fidelity \textbf{after} optimization has finished, e.g. by propagating the $N^2$ basis states $B_{kj}$ to final time $T$ using the optimized control parameter, or by propagating only $N+1$ initial states to get an estimate thereof.

Note that for pure-state preparation, we have $\rho^{target} = \boldsymbol{e}_m \boldsymbol{e}_m^\dagger$ and therefor the fidelity can be computed from $F = \frac{1}{n_{init}} \sum_i \left[\rho_i(T)\right]_{mm}$, for the $m$-th diagonal element of the final states $\rho_i(T)$.

\section{Implementation}

  \subsection{Vectorization}
  Quandary solves Lindblad's master equation \eqref{mastereq} in vectorized form with $q(t) := \text{vec}(\rho(t)) \in \C^{N^2}$ (columnwise vectorization). Using the
  relations
  \begin{align}
   \text{vec}(AB) &= (I_N\otimes A)\text{vec}(B) = (B^T\otimes I_N)\text{vec}(A)
    \\
   \text{vec}(ABC) &= (C^T\otimes A)\text{vec}(B)
  \end{align}
  for square matrices $A,B,C\in\C^{N\times N}$, the vectorized
  form of the Lindblad master equation is given by:
  \begin{align}\label{mastereq_vectorized}
    &\dot q(t) = M(t) q(t) \quad  \text{where} \\
    &M(t) := -i(I_N\otimes H(t) - H(t)^T \otimes I_N) + \sum_{k=0}^{Q-1}\sum_{l=1}^2 \gamma_{lk}
    \left( \Ell_{lk}\otimes \Ell_{lk} - \frac 1 2 \left( I_N\otimes
    \Ell^T_{lk}\Ell_{lk} + \Ell^T_{lk}\Ell_{lk} \otimes I_N \right) \right)
  \end{align}
   with $M(t) \in \C^{N^2\times N^2}$, and $H(t) = H_d(t) + H_c(t)$ being the rotating frame system and control Hamiltonians as in \eqref{eq:Hd_rotating} and \eqref{eq:Hc_rotating}, respectively.

  \subsubsection{Real-valued system and state storage}
   Quandary solves the vectorized master equation \eqref{mastereq_vectorized} in
   real-valued variables with $q(t) = u(t) + iv(t)$, evolving the real-valued
   states $u(t), v(t)\in \R^{N^2}$ with
   \begin{align}
     \dot q(t) = M(t) q(t) \quad \Leftrightarrow \quad \begin{bmatrix} \dot u(t) \\ \dot v(t) \end{bmatrix} =
   \begin{bmatrix} A(t) & -B(t) \\ B(t) & A(t) \end{bmatrix}
   \begin{pmatrix} u(t) \\ v(t) \end{pmatrix}
   \label{realvaluedODE}
   \end{align}
   for real and imaginary parts $A(t) = \mbox{Re} \left(M(t)\right)$ and $B(t) = \mbox{Im}\left(M(t)\right)$.

The real and imaginary parts of $q(t)$ are stored in a colocated manner: For
  $q = u+iv$ with $u,v\in\R^{N^2}$, a vector of size $2N^2$ is stored that
  staggers real and imaginary parts behind each other for each component:
  \begin{align*}
    q = u+iv = \begin{bmatrix}
     u^1\\u^2\\ \vdots \\ u^{N^2-1}
    \end{bmatrix}
    + i \begin{bmatrix}
     v^1\\v^2\\ \vdots \\ v^{N^2-1}
    \end{bmatrix}
    \quad \Rightarrow \quad
    q_{store} = \begin{bmatrix}
      u_1 \\ v_1\\ u_2 \\ v_2 \\ \vdots \\ u_{N^2-1} \\ v_{N^2-1}
    \end{bmatrix}
  \end{align*}

 \subsection{Sparse-matrix vs. matrix-free solver}

   In Quandary, two versions to evaluate the right hand side of Lindblad's
   equation, $M(t)q(t)$, of the vectorized real-valued system are available:
   \begin{itemize}
     \item The \textit{sparse-matrix solver} uses PETSc's sparse matrix format (sparse AIJ) to set up (and store) the time-independent building blocks inside $A(t)$ and $B(t)$. Sparse matrix-vector products are then applied at each time-step to evaluate the products $A(t)u(t) - B(t) v(t)$ and $B(t)u(t) + A(t)v(t)$.

     For developers, the appendix provides details on each term within $A(t)$ and $B(t)$ which can be matched to the implementation in the code (class \texttt{MasterEq}).

     \item The \textit{matrix-free solver} considers the state density matrix $\rho\in C^{N\times N}$ to be a tensor of rank $2Q$ (one axis for each subsystems for each matrix dimension, hence $2\cdot Q$ axes). Instead of storing the matrices within $M(t)$, the matrix-free solver applies tensor contractions to realize the action of $A(t)$ and $B(t)$ on the state vectors.

    In our current test cases, the matrix-free solver is much faster than the sparse-matrix solver (about 10x), no surprise. However the matrix-free solver is currently only implemented for systems consisting of \textbf{2, 3, 4, or 5} subsystems.

    \textbf{Important note:} The matrix-free solver currently does not parallelize across the system dimension $N$, hence the state vector is \textbf{not} distributed (i.e. no parallel Petsc!). The reason why we did not implement that yet is that $Q$ can often be large while each axis can be very shor (e.g. modelling $Q=12$ qubits with $n_k=2$ energy levels per qubit), which yields a very high-dimensional tensor with very short axix. In that case, the standard (?) approach of parallelizing the tensor along its axes will likey lead to very poor scalability due to high communication overhead. We have not found a satisfying solution yet - if you have ideas, please reach out, we are happy to collaborate!
   \end{itemize}

    \subsection{Time-stepping}
    To solve the vectorized master equation \eqref{mastereq_vectorized}, $\dot
    q(t) = M(t) q(t)$ for $t\in [0,T]$, Quandary applies a time-stepping integration
    scheme on a uniform time discretization grid $0=t_0 < \dots t_{N} = T$, with
    $t_n = n \delta t$ and $\delta t = \frac{T}{N}$, and approximates the
    solution at each discrete time step $q^{n} \approx q(t_n)$. It is a second-order accurate, symplectic Runge-Kutta scheme tableau
    \begin{tabular}{ c | c }
      $1/2$ & $ 1/2$ \\
      \hline
                &  $1$
    \end{tabular}.
    Given a state $q^n$ at time $t_n$, the update formula to compute $q^{n+1}$
    is hence
    \begin{align}
      q^{n+1} = q^n + \delta t k_1 \quad \text{where} \, k_1 \, \text{solves}
      \quad \left( I-\frac{\delta t}{2} M^{n+1/2} \right) k_1 = M^{n+1/2}  q^n
    \end{align}
    where $M^{n+1/2} := M(t_n + \frac{\delta t}{2})$. In each time-step, we
    first solve a linear equation to get the stage variable $k_1$, then use it
    to update $q^{n+1}$.

    While the implicit midpoint rule is the default (and preferred) setting in Quandary, there is the possibility to apply any time-stepping integrators provided by PETSc (class \texttt{TS} within PETSc). However, implementation needs to be verified, and adjoint time-stepping with Petsc is currently not available.

    \subsubsection{Choice of the time-step size}
    In order to choose a time-step size $\delta t$, an eigenvalue analysis of
    the constant drift Hamiltonian $H_d$ is often useful. Since $H_d$ is Hermitian, there exists a transformation $Y$ such that $Y^{\dagger}H_d Y = \Lambda \qquad  \text{where} \quad Y^{\dagger} = Y$ where $\Lambda$ is a diagonal matrix containing the eigenvalues of $H_d$.
       Transform the state $\tilde q = Y^{\dagger} q$, then the ODE transforms to
       \begin{align*}
         \dot \tilde q = -i \Lambda \tilde q \quad \Rightarrow \dot \tilde q_i =
         -i\lambda_i \tilde q_i \quad \Rightarrow \tilde q_i = a
         \exp(-i\lambda_i t)
       \end{align*}
       Therefore, the period for each mode is $\tau_i =
       \frac{2\pi}{|\lambda_i|}$, hence the shortest period is $\tau_{min} =
       \frac{2\pi}{\max_i\{|\lambda_i|\}}$. If we want $p$ discrete time points
       per period, then $p\delta t = \tau_{min}$, hence
       \begin{align} \label{eq:timestepsize}
         \delta t = \frac{\tau_{min}}{p} = \frac{2\pi}{p\max_i\{|\lambda_i|\}}
       \end{align}
       Usually, for a first order scheme we would use something like $p=20$,
       second order may use $p=10$. The above estimate provides a first idea on how big (small) the time-step size should be, and the user is advised to consider this estimate himself when running a test case. However, the estimate ignores contributions from the control Hamiltonian, where larger control amplitudes will require smaller and smaller time-steps in order to resolve (a) the time-varying controls themselves and (b) the dynamics induced by large control contributions. A standard $\Delta t$ test should be performed to varify if the time-step is small enough (testing for second order convergence of the error when reducing $\Delta t$).

       If one wants to include the time-varying Hamiltonian part $H = H_d +
       H_c(t)$ in the analysis, one could use the constraints on the control
       parameter amplitudes to remove the time-dependency using their large
       value instead.

  \subsection{Gradient computation via discrete adjoint backpropagation}
   Quandary computes the gradients of the objective function with respect to the design variables $\boldsymbol{\alpha}$ using the discrete adjoint method. The discrete adjoint approach yields exact and consistent gradients on the algorithmic level, at costs that are independent of the number of design variables.
   To that end, the adjoint approach propagates local sensitivities backwards through the time-domain while concatenating contributions to the gradient using the chain-rule.

  The consistent discrete adjoint time-integration step for
    adjoint variables denoted by $\bar q^{n}$ is given by
   \begin{align}
      \bar q^{n} = \bar q^{n+1} + \delta t \left(M^{n+1/2}\right)^T \bar k_1
      \quad \text{where} \, \bar k_1 \, \text{solves} \quad \left(
      I-\frac{\delta t}{2} M^{n+1/2}\right)^T  \bar k_1 = \bar q^{n+1}
    \end{align}
  The contribution to the gradient $\nabla J$ for each time step is
    \begin{align}\label{eq:gradient}
      \nabla J += \delta t \left( \frac{\partial M^{n+1/2}}{\partial z}
      \left(q^n + \frac{\delta t}{2} k_1\right) \right)^T\bar k_1
    \end{align}

    Each evaluation of the gradient $\nabla J$ involes a forward solve of $n_{init}$ initial quantum states to evaluate the objective function at final time $T$, as well as $n_{init}$ backward solves to compute the adjoint states and the contributions to the gradient. Note that the gradient computation \eqref{eq:gradient} requires the states and adjoint states at each time step, hence the states $q^n$ need to be stored during forward propagation.

  \subsection{Optimization algorithm}
    Quandary utilized Petsc's \texttt{Tao} optimization package to apply gradient-based iterative updates to the control variables. The \texttt{Tao} optimization interface takes routines to evaluate the objective function as well as the gradient computation. In the current setting in Quandary, \texttt{Tao} applies a nonlinear Quasi-Newton optimization scheme using a preconditioned gradient based on L-BFGS updates to approximate the Hessian of the objective function. A projected line-search is applied to ensure that the objective function yields sufficient decrease per optimization iteration while keeping the control parameters within the prescribed box-constraints.

    \subsection{Parallelization}
    Quandary offers three levels of parallelization using MPI.
    \begin{enumerate}
    \item Parallelization over initial conditions: The $n_{init}$ initial conditions $\rho_i(0)$ can be distributed over \texttt{np\_init} compute units. Since initial condition are propagated throught the Lindblad solver independently from each other, speedup from distributed initial conditions is ideal.
    \item Parallel linear algebra with Petsc (sparse-matrix solver only): For the sparse-matrix solver, Quandary utilizes Petsc's parallel sparse matrix and vector storage to distribute vectors onto \texttt{np\_petsc} compute units. To perform scaling results, make sure to disable code output (or reduce the output frequency to print only the last time-step), because writing the data files invokes additional MPI calls to gather data on the master node.
    \item Time-parallelization via XBraid (experts only): working implementation, but
      speedup depends on the test case and XBraid setting (experts only),
      configuration option is \texttt{np\_braid}
    \end{enumerate}
    Strong scaling studies are presented in \cite{guenther2021quantum}.

    In the main code, the global communicator (MPI\_COMM\_WORLD) is split into
    three sub-communicator, one for each of the above. The total number of executing MPI
    processes ($np_{total}$) is split into three subgroups in such a way that
    \begin{align*}
      np_{braid} * np_{init} * np_{petsc} = np_{total}.
    \end{align*}
    The user specifies the size of the communicator for distributing the
    initial conditions ($np_{init}$) as well as time-parallelization
    ($np_{braid}$) in the configuration file. The number of cores for parallel linear
    algebra ($np_{petsc}$) is then computed from the above equation. The
    following requirements for parallel distribution must be considered when
    setting up parallel runs:
    \begin{itemize}
    \item $\frac{n_{init}}{np_{init}} \in \mathds{N}$, where $n_{init}$ is
      the number of initial conditions that are considered (being $N^2$ for
      the full basis, $N$ for considering diagonals only as initial
      condition, and $1$ if propagating only one initial condition e.g.
      reading from file, or pure state initialization). This requirement
      ensures that each processor group owns the same number of initial
      conditions.
    \item $\frac{np_{total}}{np_{init}*np_{braid}} \in \mathds{N}$, so that
      each processor group has the same number of cores for Petsc.
    \item $\frac{N^2}{np_{petsc}} \in \mathds{N}$, hence the system
      dimensions must be integer multiple of the number of cores for
      distributing linear algebra in Petsc. This constraint is a little
      annoying, however the current implementation requires this due to the
      colocated storage of the real and imaginary parts of the vectorized
      state.
    \end{itemize}

\subsection{Testing}
  \begin{itemize}
    \item Quandary has a set of regression tests. Please take a look at the REGRESSIONTEST.md document in the \verb+quandary/tests+ directory for instruction on how to run the regression tests.
    \item In order to check if the gradient implementation is correct, one can choose to run a Central Finite Difference test. Let the overall objective function be denoted by $F(\boldsymbol{\alpha})$. The Central Finite Difference test compares each element of the gradient $\nabla F(\boldsymbol{\alpha})$ with the following (second-order accurate) estimate:
    \begin{align*}
       \left(\nabla F(\boldsymbol{\alpha}) \right)_i \approx \frac{F(\bfa + \epsilon\bs{e}_i) - F(\bfa - \epsilon\bs{e}_i)}{2\epsilon} \qquad \qquad \text{(CFD)}
    \end{align*}
    for unit vectors $\bs{e}_i\in \R^d$, and $d$ being the dimension of $\bfa$.

    To enable the test, set the flag for the compiler directive \texttt{TEST\_FD\_GRAD} at the beginning of the \texttt{src/main.cpp} file. Quandary will then iterate over all elements in $\alpha$ and report the \textit{relative} error of the implemented gradient with respect to the ``true'' gradient computed from CFD.

  \end{itemize}

\section*{Acknowledgments}
This work was performed under the auspices of the U.S. Department of Energy by Lawrence
Livermore National Laboratory under Contract DE-AC52-07NA27344. LLNL-SM-818073.

This document was prepared as an account of work sponsored by an agency of the United States
government. Neither the United States government nor Lawrence Livermore National Security, LLC,
nor any of their employees makes any warranty, expressed or implied, or assumes any legal
liability or responsibility for the accuracy, completeness, or usefulness of any information,
apparatus, product, or process disclosed, or represents that its use would not infringe
privately owned rights. Reference herein to any specific commercial product, process, or service
by trade name, trademark, manufacturer, or otherwise does not necessarily constitute or imply
its endorsement, recommendation, or favoring by the United States government or Lawrence
Livermore National Security, LLC. The views and opinions of authors expressed herein do not
necessarily state or reflect those of the United States government or Lawrence Livermore
National Security, LLC, and shall not be used for advertising or product endorsement purposes.

\bibliographystyle{plain}
\bibliography{mybib}

\appendix
 \section{Appendix: Details for the real-valued, vectorized Hamiltonian}
   To assemble (evaluate)
   $A(t) = Re(M(t))$ and $B(t) = Im(M(t))$, consider
   \begin{align}
    iH &= iH_d(t) + iH_c(t) \\
      &= i\left( \sum_k (\omega_k - \omega_k^{\text{rot}}) a_k^\dagger a_k - \frac{\xi}{2}a_k^\dagger a_k^\dagger a_k a_k  - \sum_{l>k}  \xi_{kl} a_k^\dagger a_k a_l^\dagger a_l +  \sum_{l>k} J_{kl} \cos(\eta_{kl}t)\left(a_k^\dagger a_l + a_ka_l^\dagger \right)
       \right.\\
      & \left. \quad \quad + \sum_k p^k(\vec{\alpha}^k,t) \left(a_k + a_k^\dagger\right) \right)\\
      &+ \left( \sum_k \sum_{kl} - J_{kl} \sin(\eta_{kl}t) \left(a_k^\dagger a_l - a_ka_l^\dagger\right)  - \sum_k q^k(\vec{\alpha}^k, t)\left(a_k - a_k^\dagger\right)\right)
   \end{align}
   Hence $A(t)$ and $B(t)$ are given by
   \begin{align}
    A(t) &= A_d + \sum_k  q^k(\vec{\alpha}^k,t) A_c^k + \sum_{l>k} J_{kl} \sin(\eta_{kl}t)  A_d^{kl} \\
   \text{with} \quad  A_d &:= \sum_k \sum_{j=1,2} \gamma_{jk} \left( \Ell_{jk}\otimes\Ell_{jk} - \frac 1 2 \left(I_N \otimes \Ell_{jk}^T\Ell_{jk} + \Ell_{jk}^T\Ell_{jk}\otimes I_N\right) \right)\\
    A_c^k &:=  I_N \otimes \left(a_k - a_k^\dagger\right) - \left(a_k - a_k^\dagger\right)^T\otimes I_N \\
    A_d^{kl} &:=  I_N\otimes \left(a_k^\dagger a_l - a_k a_l^\dagger\right) - \left(a_k^\dagger a_l - a_k a_l^\dagger\right)^T\otimes I_N
   \end{align}
   and
   \begin{align}
     B(t) &=  B_d + \sum_k p^k(\vec{\alpha}^k,t) B_c^k + \sum_{kl} J_{kl} \cos(\eta_{kl}t)B_d^{kl}\\
     \text{with} \quad B_d &:= \sum_k (\omega_k - \omega_k^{\text{rot}}) \left(-I_N \otimes a_k^\dagger a_k + (a_k^\dagger a_k)^T \otimes I_N \right) - \frac{\xi_k}{2}\left(- I_N \otimes a_k^\dagger a_k^\dagger a_k a_k + (a_k^\dagger a_k^\dagger a_k a_k )^T\otimes I_N\right)  \\
       &\quad - \sum_{l>k}  \xi_{kl} \left(-I_N \otimes a_k^\dagger a_k a_l^\dagger a_l + (a_k^\dagger a_k a_l^\dagger a_l)^T \otimes I_N \right)\\
       B_c^k &:=  - I_N \otimes \left(a_k + a_k^\dagger\right) + \left(a_k + a_k^\dagger\right)^T\otimes I_N \\
       B_d^{kl} &:=  - I_N\otimes \left(a_k^\dagger a_l + a_k a_l^\dagger\right) + \left(a_k^\dagger a_l + a_k a_l^\dagger\right)^T\otimes I_N \\
   \end{align}

  The sparse-matrix solver initializes and stores the constant matrices
       $A_d, A_d^{kl}, A_c^k, B_d, B_d^{kl}, B_c^k$ using Petsc's sparse-matrix format. They are used
       as building blocks to evaluate the blocks in the system matrix $M(t)$ with
     \begin{align}
       A(t) &= Re(M(t)) = A_d + \sum_k q^k(\alpha^k, t)A_c^k + \sum_{l>k} J_{kl} \sin(\eta_{kl}t) A_d^{kl}\\
       B(t) &= Im(M(t)) = B_d + \sum_k p^k(\alpha^k, t)B_c^k + \sum_{kl} J_{kl} \cos(\eta_{kl}t) B_d^{kl}
     \end{align}
   at each time $t$, which are applied to the vectorized, real-valued density matrix using Petsc's sparse MatVec implementation.

  The matrix-free solver does not explicitly store the matrices $A_d,B_d,
       A_c^k, B_c^k$, etc., but instead only evaluates their action on a vector $q(t)$ using tensor contractions applied to the corresponding dimension of the density matrix tensor.

\end{document}


\documentclass{memoir}
\usepackage{notestemplate}

%\logo{~/School-Work/Auxiliary-Files/resources/png/logo.png}
%\institute{Rice University}
%\faculty{Faculty of Whatever Sciences}
%\department{Department of Mathematics}
%\title{Class Notes}
%\subtitle{Based on MATH xxx}
%\author{\textit{Author}\\Gabriel \textsc{Gress}}
%\supervisor{Linus \textsc{Torvalds}}
%\context{Well, I was bored...}
%\date{\today}

\begin{document}

% \maketitle

% Notes taken on 02/22/21

\section{Subrepresentations and Irreducibility}
\label{sec:subrepresentations_and_irreducibility}

Let \(K\) be a field and \(G\) a group. Recall that if \(\textrm{dim}_KV = n\), we can identify the group \(GL(V)\) with \(GL_n(K)\), the group of invertible \(K \)-linear operators on \(V\) under composition.

\begin{defn}[Subrepresentations]
	Let \(\rho:G\to GL(V)\) be a representation of \(G\). Suppose that \(W\) is a subspace of \(V\) which is \(G\)-invariant. That is, for all \(w \in W\), \(g \in G\), it holds that  \(\rho_g(w)\in W\). Then \(W\) becomes a representation of \(G\) and we say that
	\begin{align*}
		(W,\rho w:G\to GL(W))\\
		g\mapsto \left[ \rho_g\mid W:W\to W \quad w \mapsto \rho_g(w) \right]
	\end{align*}
	is a \textbf{subrepresentation} of \((V,\rho)\).
\end{defn}

% Examples

\begin{defn}
	The \textbf{direct sum} of two representations of \(G\), \((V',\rho_V')\) and \((V'',\rho_V'')\) is the representation of \(G\) given by:
	\begin{align*}
		(V:=V'\bigoplus V'', \rho_{V'\bigoplus V''}:G \to GL(V) )\\
		g\mapsto \left[ \rho_g:V\to V \quad v = v'+v''\mapsto \rho_g'(v') + \rho_g''(v'') \right]
	\end{align*}
\end{defn}
If we fix a basis for both \(V'\) and \(V''\), then their union is a basis of \(V = V'\bigoplus V''\).

% Examples

\begin{defn}[Irreducible]
	A representation is called \textbf{irreducible} if it contains no proper subrepresentations-- otherwise it is called \textbf{reducible}. A representation is called \textbf{completely reducible} if it decomposes as a direct sum of irreducible subrepresentations.
\end{defn}
Irreducible representations will turn out to be the building blocks of group representation theory. This is complemented by Mascinke's Theorem, which will state that every \(\C\)-linear representation of a finite group \(G\) of finite degree is completely reducible.

% Example (Necessary)

\end{document}

Abstract Algebra - Introductory/Algebra II/Notes/source/Lecture10 - Subrepresentations.tex